Skip to content

Bun.serve websocket: send a Close frame with 1002/1007/1009 when failing the connection - #35874

Open
robobun wants to merge 3 commits into
mainfrom
farm/bb9169f5/ws-fail-close-codes
Open

robobun wants to merge 3 commits into
mainfrom
farm/bb9169f5/ws-fail-close-codes

Conversation

@robobun

@robobun robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator

What

When Bun.serve's WebSocket parser detects a protocol violation (bad masking, reserved opcode/RSV bits, invalid fragment state, invalid UTF-8, oversized payload, or an invalid inbound Close body), it now writes a Close frame carrying the RFC 6455 7.4.1 status code before closing TCP, instead of tearing the socket down bare.

Why

RFC 6455 7.1.7 says an endpoint that Fails the WebSocket Connection SHOULD send a Close frame with the appropriate status code before closing the underlying TCP connection. Every violation-detection branch in WebSocketProtocol.h / WebSocketContext.h previously called forceClose() = a bare us_socket_close(). A conforming client therefore only ever saw 1006 (abnormal closure) no matter why the server rejected the frame, and the diagnostic reason string only rode the socket-close side channel into the app's close(1006, "...") callback.

Separately, parseClosePayload mapped an invalid inbound close code, an invalid-UTF-8 reason, or a 1-byte body to the sentinels {1006, ...} / {1005, ...}, which formatClosePayload refuses to serialize, so the server answered with an empty-payload Close (peer sees 1005) instead of 1002/1007.

How

  • Added Impl::failConnection(wState, s, code, reason) in WebSocketContext.h that routes through the existing WebSocket::end() path: format Close payload, send(..., OpCode::CLOSE), set isShuttingDown so later inbound bytes are ignored, half-close (FIN) once drained.
  • Pointed all twelve violation sites at it:
    • 1002 CLOSE_PROTOCOL_ERROR: masking mismatch, RSV1/RSV2/RSV3, reserved/unknown opcode, fragmented or >125-byte control frame, continuation with no message in progress, new data frame interleaved in a fragmented message.
    • 1007 CLOSE_INVALID_DATA: TEXT frame or Close reason that is not valid UTF-8.
    • 1009 CLOSE_MESSAGE_TOO_BIG: single frame, fragmented total, or post-inflation size over maxPayloadLength.
  • parseClosePayload now returns 1002 for an invalid/reserved code and for a 1-byte body (RFC 6455 5.5.1: if there is a body, its first two bytes MUST be the status code), and 1007 for an invalid-UTF-8 reason.
  • forceClose() is retained only for the idle-timeout path.

The app's close() handler now receives the same code and reason that go out on the wire (matching the ws module) instead of 1006 plus the side-channel diagnostic.

Verification

test/js/bun/websocket/websocket-server-fail-close-codes.test.ts is a raw RFC 6455 TCP client against Bun.serve({websocket}) that asserts a Close frame (0x88) with the mandated code arrives on the wire before the TCP close, plus what the app handler observed. 16 violation cells + 3 controls.

# without the fix (USE_SYSTEM_BUN=1)
 3 pass
 16 fail

# with the fix (bun bd)
 19 pass
 0 fail

Existing websocket-server-unmasked-frames.test.ts, websocket-server-rsv-frames.test.ts, and the maxPayloadLength case in websocket-server.test.ts are updated to expect the new codes (1002/1009) that the app handler now sees.


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/websocket/websocket-server.test.ts

…ing the connection

RFC 6455 7.1.7 says an endpoint that Fails the WebSocket Connection
SHOULD send a Close frame with the appropriate status code before
closing the underlying TCP connection. Bun.serve's parser previously
called us_socket_close() directly for every protocol violation, so a
conforming client only ever observed 1006 (abnormal closure) regardless
of whether the server rejected the frame for bad masking, an invalid
opcode, invalid UTF-8, or an oversized payload.

Add an Impl::failConnection hook that routes through the existing
WebSocket::end() path (format Close payload, send, set isShuttingDown,
half-close once drained) and point every violation-detection site at it
with the status code 7.4.1 mandates:

  1002  protocol error (masking, RSV bits, opcode, fragment state,
        invalid/reserved close code, 1-byte close body)
  1007  invalid UTF-8 text or close reason
  1009  message too big (raw or post-inflation)

parseClosePayload now returns 1002/1007 for an invalid close body
instead of the 1005/1006 sentinels that formatClosePayload refuses to
serialize, so the reply is a real Close frame rather than an empty one.

The app's close() handler now receives the same code and reason the peer
sees on the wire (matching the ws module) instead of 1006 plus a
diagnostic string carried on the socket-close side channel. forceClose
is kept only for the idle-timeout path.
@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:27 PM PT - Jul 25th, 2026

❌ @robobun, your commit 0b7a450 has 1 failures in Build #82126 (All Failures):

  • 📦 Binary size — 12 over 0.50 MB
  • targetthis build canary: main #79916
    sizeΔ
    ❌ bun-darwin-aarch6458.13 MB57.58 MB+564.9 KB
    ❌ bun-darwin-x6463.48 MB62.95 MB+544.5 KB
    ❌ bun-linux-aarch6470.98 MB70.42 MB+576.0 KB
    ❌ bun-linux-x6472.47 MB71.95 MB+528.0 KB
    ❌ bun-linux-aarch64-musl64.88 MB64.32 MB+576.0 KB
    ❌ bun-linux-x64-musl66.98 MB66.45 MB+544.0 KB
    ❌ bun-linux-aarch64-android78.47 MB77.97 MB+512.0 KB
    ❌ bun-linux-x64-android80.62 MB80.10 MB+529.2 KB
    ❌ bun-freebsd-x6483.07 MB82.56 MB+528.0 KB
    ❌ bun-freebsd-aarch6484.84 MB84.31 MB+544.0 KB
    ❌ bun-windows-x6480.26 MB79.70 MB+570.0 KB
    ❌ bun-windows-aarch6470.86 MB70.34 MB+533.0 KB

    Add [skip size check] to the commit message if this increase is intentional.


🧪   To try this PR locally:

bunx bun-pr 35874

That installs a local version of the PR into your bun-35874 executable, so you can run:

bun-35874 --bun

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

WebSocket protocol and message-processing failures now send explicit Close frames with codes 1002, 1007, or 1009. Close-payload validation distinguishes malformed status codes from invalid UTF-8 reasons, and tests cover wire-level responses and callback behavior.

WebSocket close error handling

Layer / File(s) Summary
Protocol error classification
packages/bun-uws/src/WebSocketProtocol.h
Adds close-code constants, refines Close payload validation, and routes masking, RSV, opcode, fragmentation, and control-frame errors through failConnection.
Message-processing close frames
packages/bun-uws/src/WebSocketContext.h
Routes decompression, invalid UTF-8, and payload-limit failures through Close frames for complete and fragmented messages.
Server close behavior tests
test/js/bun/websocket/websocket-server-fail-close-codes.test.ts, test/js/bun/websocket/websocket-server-rsv-frames.test.ts, test/js/bun/websocket/websocket-server-unmasked-frames.test.ts, test/js/bun/websocket/websocket-server.test.ts
Adds raw TCP coverage and updates existing expectations for protocol, invalid-data, and message-too-big close codes and responses.

Possibly related issues

Suggested reviewers: cirospaciari

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly states the main behavior change and the affected WebSocket close codes.
Description check ✅ Passed It includes the change, rationale, implementation, and verification, though the headings differ from the template.

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Bun.serve websocket: treat a 1-byte Close body as a protocol error #33691 - Both rewrite parseClosePayload in WebSocketProtocol.h to correctly handle malformed Close frames; Bun.serve websocket: send a Close frame with 1002/1007/1009 when failing the connection #35874 fully subsumes Bun.serve websocket: treat a 1-byte Close body as a protocol error #33691's 1-byte body fix

🤖 Generated with Claude Code

@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Confirmed: this subsumes #33691. That PR maps a 1-byte Close body to the 1006 sentinel, which formatClosePayload still refuses to serialize, so the peer still sees an empty Close (1005). This PR maps it to 1002 with a reason and actually writes it to the wire, along with every other violation class. #33691 can be closed once this lands.

Comment thread packages/bun-uws/src/WebSocketContext.h
Comment thread test/js/bun/websocket/websocket-server-fail-close-codes.test.ts Outdated
robobun added 2 commits July 26, 2026 04:30
…ser hot path

failConnection is reached from a dozen error branches inside the
templated consume()/consumeMessage()/handleFragment() bodies. With
end() and send() both header-inline, the optimizer was cloning the
full close-frame-and-shutdown path at every branch of every template
instantiation. One out-of-line copy per <SSL, isServer, USERDATA>
instantiation is enough for a cold failure path.

(The +500KB reported by the size check on build 82105 is mostly a
stale baseline: main #79916 predates the quic/repl/inspector merges
this branch is based on.)
@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

The binary-size check is comparing against a stale baseline. Build #82105 (before noinline) and build #82126 (after noinline) report byte-identical deltas on every target (e.g. linux-x64 +528.0 KB, darwin-aarch64 +564.9 KB), so this diff contributes effectively nothing to it.

The baseline is main #79916 at ae4b17de6d, which is 12 commits behind this branch's base 44f6469e0d. Those 12 commits include #32602 (node:quic on lsquic, HTTP/3), #31827 (node:repl with 82 vendored tests), and #31823 (node:inspector DevTools-protocol server), which account for the reported growth.

Tests are green apart from two yellow retries (napi.test.ts, express/res.sendFile.test.ts) unrelated to WebSocket. Leaving the noinline on failConnection in place as it is the right call for a cold failure path inside the templated parser regardless.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found beyond the two resolved nits; deferring for a maintainer to sign off on the user-visible behavior change (app close() handler now sees 1002/1007/1009 instead of 1006) and confirm CI is green across platforms.

What was reviewed:

  • All twelve forceClose → failConnection sites return/propagate true back to consume(), and end() sets isShuttingDown before the outer onData uncork, so no double-processing after the Close is queued.
  • CloseFrame::message widening to const char *: only two callers, both feed it into a std::string_view for end(); no writes through it.
  • WebSocketProtocol's only Impl instantiation is WebSocketContext, so no other consumer needs failConnection added; __attribute__((noinline)) is fine under clang-cl on Windows (used unguarded elsewhere in src/jsc/bindings/).
Extended reasoning...

Overview

This PR replaces bare-TCP forceClose() with a new failConnection() → WebSocket::end(code, reason) at all twelve protocol-violation branches in uWebSockets' WebSocketProtocol.h/WebSocketContext.h, so Bun.serve now writes an RFC 6455 Close frame carrying 1002/1007/1009 before dropping TCP. parseClosePayload is reworked to return 1002 for a 1-byte body or invalid/reserved code and 1007 for a non-UTF-8 reason (previously it returned the 1005/1006 sentinels that formatClosePayload refuses to serialize). A new 19-case wire-level test file covers every violation class plus three well-formed controls, and three existing test files are updated to expect the new codes the app handler now receives.

Security risks

None identified. The only new outbound write on the violation path is a ≤125-byte Close frame; end() immediately arms the short ping-timeout and half-closes once drained, so a hostile client cannot hold the connection open longer than before. Input validation is unchanged — the same predicates fire, only the response differs. No new allocation on the failure path beyond the fixed-size stack closePayload buffer already used for normal closes.

Level of scrutiny

This is a behavior change in the vendored uWS WebSocket parser hot path and it changes what user code observes in the websocket.close(ws, code, reason) handler (1006 → 1002/1007/1009). That is intentional, RFC-aligned, and matches Node's ws, but it is user-visible and touches a critical networking codepath, so it warrants a maintainer look rather than bot approval. The 0b7a450 follow-up adds __attribute__((noinline)) to keep end()/send() from being inlined at a dozen templated error sites — a code-size decision a human should also glance at.

Other factors

Both of my earlier inline nits are resolved: the probe() cleanup was fixed in b5bde3d (using server + try/finally), and the 1009-vs-1007 conflation for InflationStream::inflate() failures was acknowledged as a follow-up requiring a PerMessageDeflate.h refactor. I verified parseClosePayload has no callers outside WebSocketContext.h, that the CloseFrame::message const-qualification change is compatible with both call sites, and that WebSocketContext is the sole Impl template argument to WebSocketProtocol so nothing else needs the new failConnection static. The test coverage is thorough (raw-TCP framing client asserting both wire bytes and app-handler view) and includes controls proving well-formed frames are not over-rejected.

Jarred-Sumner pushed a commit that referenced this pull request Sep 25, 2026
…he peer (#43013)

### Problem

- With `Bun.serve({ tls })`, `ws.terminate()` waits for the peer's TLS
close_notify. A peer that does not read never sends it: the `close`
handler does not run, the fd stays open, and `message()` keeps firing.
Each inbound frame re-arms the idle timeout, so nothing bounds the wait.
Plain `ws://` closes at once.
- The cause is `WebSocket::close()`
(`packages/bun-uws/src/WebSocket.h:76`): `us_socket_close(s, 0,
nullptr)`. On TLS, code 0 is the graceful close
(`us_internal_ssl_close`,
`packages/bun-usockets/src/crypto/openssl.c:2194`). `forceClose()` with
no reason (a reserved opcode) passes the same code.

### Fix

- `WebSocket::close()` closes with `FAST_SHUTDOWN` (a FIN, no
close_notify). If the socket is still open, usockets parked the close
behind its ciphertext spill, and `close()` closes again with
`CONNECTION_RESET` (the idiom of #39548). `forceClose()` with no reason
calls `close()`.
- Correct because every caller relies on the close event having run on
return (`CloseCode`, `src/uws_sys/us_socket_t.rs:23`). Plain sockets
treat codes 0 and 2 alike. A peer that keeps up sees no change. Under
send backpressure the close is now a reset (see Notes).
- Not changed: the other code 0 closes in uWS (HTTP, HTTP/2), listed in
the Notes.
- Verified: new
`test/js/bun/websocket/websocket-server-forced-close.test.ts`, the 3
`wss` cases fail without the fix. Also `test/js/bun/websocket/`,
`test/js/web/websocket/`, `test/js/first_party/ws/`.

### Background

- `us_socket_close(s, code, reason)` closes the fd and runs the close
handler. On TLS, code 0 sends close_notify and waits for the peer, code
1 resets, and code 2 sends a FIN but waits while a spill is pending.
- A spill is TLS ciphertext that bun reported as written, but the kernel
did not accept yet.
- uWS `close()` is the forced close (1006, no Close frame). `end()`, the
graceful close, does not change.
- #38243 fixed this wait for the client `WebSocket`.

<details><summary>Notes</summary>

**Repro** (1.4.3, canary c6b7fcb, and a debug build of main
b8eacea). A `wss://` server sends one message. The client calls
`pause()` and sends one message back. The server calls `ws.terminate()`
in `message()`.

```
wss, before:  0.0s SERVER terminate(), readyState 3
              12.0s done waiting                       (close handler never ran)
ws,  before:  0.0s SERVER close handler: 1006 ""
              0.0s SERVER terminate(), readyState 3
wss, after:   0.1s SERVER close handler: 1006 ""
              0.1s SERVER terminate(), readyState 3
```

- With `idleTimeout: 8` and a silent peer, the unfixed server closes at
8.0 s from the idle timer, with the reason "WebSocket timed out from
inactivity".
- With `idleTimeout: 4` and a paused peer that sends one frame each
second, the unfixed server never closes. In 14 s it delivered 14
messages to `message()` after `terminate()`, all with `ws.readyState ===
3`. The uWS parser stops only when `us_socket_is_closed()` or
`isShuttingDown` is true, and a deferred TLS close sets neither.
- Protocol error: a paused `Bun.connect({ tls })` client sends a masked
frame with opcode 3, and 300 ms later a valid text frame. Before: no
`close`, and the text frame reaches `message()`. After: `close` 1006 at
once. The `forceClose()` calls that pass a reason already close at once,
because the reason length is the close code and it is never 0.

**A peer that keeps up sees no change.** Matrix: `ws.send(payload);
ws.terminate()` from a handler and from a timer, payload 100 B, 64 KB,
300 KB and 4 MB, 10 runs each, a Bun `WebSocket` client that reads. All
16 cells are the same for `ws` and `wss`, before and after: the same
messages arrive, then `close 1006`, and no `error` event. A node `ws`
client also reports `close 1006` with no `error` event, before and
after.

**A peer under send backpressure gets a reset.** A spill does not prove
a dead peer. It means the last flush hit a full kernel buffer, which
also happens with a slow peer that still reads. If the first close
cannot drain the spill, the second close resets: the kernel drops its
send buffer and the peer sees ECONNRESET. Before this change that peer
got the kernel buffer and the spill, then close_notify, whenever the
spill drained. Plain `ws://` gives it the kernel buffer and a FIN. The
uWS backpressure buffer is lost in every variant, so the stream ends in
the middle of a message in every variant. The reset is the documented
cost of a close that must finish now (`CloseCode::failure` names
`terminate()`). The client `WebSocket.terminate()` (#38243) and
`Socket.terminate()` reset on every close. A FIN that keeps the kernel
buffer needs a usockets close that drops the spill without a wait, which
is the durable fix that #39548 names.

**Why a FIN first and not a reset for every TLS close.** With a node TLS
client, a server reset gives `error: ECONNRESET`. A bare FIN gives
`end`, then `close` with no error, which is what `ws://` gives today.
npm `ws` 8.18.3 does the same: its `terminate()` is `socket.destroy()`,
and the comment in `us_internal_ssl_close` records that node's destroy
sends a bare FIN.

**Why the second close.** A build with `FAST_SHUTDOWN` only still hangs
in the backpressure test (paused client, the server sends 64 KB messages
until `send()` returns -1, then `terminate()`). `us_internal_ssl_close`
defers codes 0 and 2 until the spill drains. The check after the first
close detects the deferral and does not predict it. usockets first tries
to drain the spill, so a peer that recovered still gets a FIN. A check
of `us_socket_ssl_spill_pending()` before the close is not equivalent:
it resets a peer that has recovered. #39548 records that.

The read of the socket after the first close is safe.
`us_internal_socket_close_raw` links a socket into the loop's closed
list only after the close handler returns, so JS that re-enters the
event loop from the handler cannot free it.

**Other callers of `WebSocket::close()`** that get the same fix: the
`open()` exception path in `ServerWebSocket.rs`, the HMR socket
(`hmr_socket.rs`), and `DevServer` teardown, which has
`debug_assert!(self.active_websocket_connections.is_empty())` right
after it closes every socket.

**Not fixed here.** These sites have the same exposure. Each needs its
own repro and tests, and the durable fix is a usockets close that cannot
defer (see the last Background bullet of #39548).

- `AsyncSocket<SSL>::close()` (`AsyncSocket.h:101`) and the HTTP paths
that use it. One case is reproduced: `Bun.serve({ tls, idleTimeout: 4
})` with a peer that completes the handshake and stops reading. The
plain server socket is in FIN-WAIT-2 at 12 s. The TLS server socket is
still ESTAB at 40 s.
- The code 0 closes in `Http2Context.h`.
- `JSNodeHTTPServerSocket::close()` (`JSNodeHTTPServerSocket.cpp:90`).
It already uses `FAST_SHUTDOWN`, with no second close for a spill.
- A peer FIN on a TLS WebSocket that still has a spill, and
`closeOnBackpressureLimit`. These are not forced closes.

**Order with #35874.** That PR replaces the `forceClose()` calls that
pass no reason with a graceful `end(1002)`. If it merges first, the
`reason.empty()` branch here has no caller and can go, and the reserved
opcode test must expect the code that PR reports. If this PR merges
first, #35874 needs no change to `close()`.

**Test file.** The test is a sibling file and not part of
`websocket-server.test.ts`. That file has load-related timeouts on a
debug build (8 here, in `send()`, `sendText()`, `sendBinary()` and the
benchmark). The same tests pass alone. The two `terminate()` tests fail
at once on an unfixed build, with `close: 1006 ""` missing between
`message` and `terminate() returned`. The reserved opcode test times out
there.

**Suites run** on a debug+ASAN build: all of `test/js/bun/websocket/`,
`websocket.test.js`, `websocket-pause.test.ts`,
`websocket-client.test.ts`, `websocket-close-code.test.ts`,
`websocket-close-fragmented.test.ts`, `websocket-proxy.test.ts`,
`websocket-subprotocol-strict.test.ts`,
`websocket-client-short-read.test.ts`,
`websocket-close-connecting.test.ts`,
`websocket-close-async-dispatch.test.ts`,
`websocket-buffered-amount.test.ts`,
`websocket-server-send-from-drain.test.ts`, `ws.test.ts`,
`ws-upgrade-events.test.ts`, and the pipelined upgrade tests in
`serve.test.ts`. Failures that do not come from this change: two
`websocket.test.js` tests that need `ws.postman-echo.com`, and tests
that spawn a debug build per case and go over 5 s when this machine is
loaded ("should connect many times over https",
`ws-upgrade-events.test.ts`). They pass alone.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 0 · 3 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/websocket/websocket-server-forced-close.test.ts
bun test v1.4.3 (c6b7fcb)

test/js/bun/websocket/websocket-server-forced-close.test.ts:
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns [189.90ms]
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns under backpressure [156.98ms]
(pass) ws: forced close with a peer that does not read > a frame with a reserved opcode closes the socket [139.63ms]
53 |       client.onmessage = () => {
54 |         client.pause();
55 |         client.send("terminate");
56 |       };
57 |       await terminated.promise;
58 |       expect(events).toEqual(["message: terminate", 'close: 1006 ""', "terminate() returned"]);
                          ^
error: expect(received).toEqual(expected)

  [
    "message: terminate",
-   "close: 1006 """,
    "terminate() returned",
  ]

- Expected  - 1
+ Received  + 0

      at <anonymous> (/workspace/bun/test/js/bun/websocket/websocket-server-f
... (truncated)

release without fix: all passed
bun test v1.4.3-canary.1 (fed761c)

test/js/bun/websocket/websocket-server-forced-close.test.ts:
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns [18.47ms]
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns under backpressure [16.71ms]
(pass) ws: forced close with a peer that does not read > a frame with a reserved opcode closes the socket [16.37ms]
(pass) wss: forced close with a peer that does not read > terminate() runs the close handler before it returns [17.73ms]
(pass) wss: forced close with a peer that does not read > terminate() runs the close handler before it returns under backpressure [11.70ms]
(pass) wss: forced close with a peer that does not read > a frame with a reserved opcode closes the socket [10.75ms]

 6 pass
 0 fail
 6 expect() calls
Ran 6 tests across 1 file. [85.00ms]
__F:0:S:0
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/websocket/websocket-server-forced-close.test.ts
bun test v1.4.3 (c6b7fcb)

test/js/bun/websocket/websocket-server-forced-close.test.ts:
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns [278.56ms]
(pass) ws: forced close with a peer that does not read > terminate() runs the close handler before it returns under backpressure [237.39ms]
(pass) ws: forced close with a peer that does not read > a frame with a reserved opcode closes the socket [210.23ms]
(pass) wss: forced close with a peer that does not read > terminate() runs the close handler before it returns [211.46ms]
(pass) wss: forced close with a peer that does not read > terminate() runs the close handler before it returns under backpressure [155.50ms]
(pass) wss: forced close with a peer that does not read > a frame with a reserved opcode closes the socket [71.47ms]

 6 pass
 0 fail
 6 expect() calls
Ran 6 tests across 1 file. [2.82s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 841ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/128] gen generated_host_exports.rs
generated_host_exports.rs: 121 exports (host=5, lazy=10, generic=106, rust=0); 242 extern-C blocks audited
[2/128] gen JSSink.{cpp,h,lut.h,rs}
generated_jssink.rs: 7 sinks, 84 exported symbols
Generating /workspace/bun/build/release/codegen/JSSink.lut.h from /workspace/bun/build/release/codegen/JSSink.lut.txt
[3/128] gen cpp.rs (cppbind)
[4/128] gen JS modules (bundle-modules)
Preprocess modules (11568ms)
Bundle modules (91ms)
Postprocesss modules (179ms)
Bundle Functions (587ms)
Generate Code (41ms)

[12.48s] Bundled "src/js" for production
  2600 kb
  197 internal modules
  13 native modules
  50 internal functions across 16 files
[4/26] cargo bun_runtime → libbun_runtime.a
�[1m�[33mwarning�[0m�[1m: binary `bun_shim_impl` should have a kebab-case name�[0m
   �[1m�[94m|�[0m
�[1m�[94m 1�[0m �[1m�[94m|�[0m /workspace/bun/build/release/rust-target/.../bun_shim_impl
   �[1m�[94m|�[0m                                              �[1m�[33m^^^^^^^^^^^^^�[0m
   �[1m�[94m|�[
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
packages/bun-uws/src/WebSocket.h                   |   8 +-
 packages/bun-uws/src/WebSocketContext.h            |   5 +
 .../websocket-server-forced-close.test.ts          | 152 +++++++++++++++++++++
 3 files changed, 164 insertions(+), 1 deletion(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
packages/bun-uws/src/WebSocket.h                              4      5     21
packages/bun-uws/src/WebSocketContext.h                       2      3     20
…/js/bun/websocket/websocket-server-forced-close.test.ts      1      2     20
```

</details>

<!-- robobun:evidence:end -->

---------

Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants