Skip to content

Commit a7ee48e

Browse files
jmorrellclaude
andcommitted
Fix incomplete WebSocket close handshake in Workers (error 1101)
The server side of a WebSocketPair never echoed the client's close frame, so workerd detected a hung connection that would never generate a response. Add server.close() in the close handler per Cloudflare's documented pattern, and add a test asserting wasClean === true. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2910c8c commit a7ee48e

File tree

2 files changed

+27
-0
lines changed

2 files changed

+27
-0
lines changed

src/__tests__/workers.workers.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ describe("Workers runtime integration tests", () => {
156156
});
157157

158158
describe("WebSocket functionality in Workers", () => {
159+
it("should complete close handshake when client disconnects", async () => {
160+
const response = await SELF.fetch("http://localhost/rpc", {
161+
method: "GET",
162+
headers: { Upgrade: "websocket" },
163+
});
164+
165+
const ws = response.webSocket!;
166+
ws.accept();
167+
168+
const closeEvent = new Promise<CloseEvent>((resolve) => {
169+
ws.addEventListener("close", (event) => resolve(event as CloseEvent));
170+
});
171+
172+
ws.close(1000, "normal closure");
173+
174+
const event = await closeEvent;
175+
expect(event.wasClean).toBe(true);
176+
});
177+
159178
it("should upgrade WebSocket connection with 101 response", async () => {
160179
const response = await SELF.fetch("http://localhost/rpc", {
161180
method: "GET",

src/websocket.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ export function newWorkersWebSocketRpcSession<
113113
const [client, server] = Object.values(pair);
114114
server.accept();
115115

116+
// Complete the WebSocket close handshake when the client disconnects.
117+
// Without this workerd detects a hung connection that will never
118+
// generate a response (error 1101).
119+
// https://developers.cloudflare.com/workers/observability/errors/
120+
server.addEventListener("close", () => {
121+
server.close();
122+
});
123+
116124
const transport = createWebSocketTransport(server);
117125
const session = new RpcSession<TRemote, TLocal>(transport, service ?? ({} as TLocal), {
118126
role: "acceptor",

0 commit comments

Comments
 (0)