From a4111442191fff300132259752e6d2d5613d1871 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Fri, 14 Apr 2023 22:17:39 +0530 Subject: fix(ext/websocket): Avoid write deadlock that requires read_frame to complete (#18705) Fixes https://github.com/denoland/deno/issues/18700 Timeline of the events that lead to the bug. 1. WebSocket handshake complete 2. Server on `read_frame` holding an AsyncRefCell borrow of the WebSocket stream. 3. Client sends a TXT frame after a some time 4. Server recieves the frame and goes back to `read_frame`. 5. After some time, Server starts a `write_frame` but `read_frame` is still holding a borrow! ^--- Locked. read_frame needs to complete so we can resume the write. This commit changes all writes to directly borrow the `fastwebsocket::WebSocket` resource under the assumption that it won't affect ongoing reads. --- cli/tests/unit/websocket_test.ts | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'cli/tests') diff --git a/cli/tests/unit/websocket_test.ts b/cli/tests/unit/websocket_test.ts index 6737edc11..948e2add2 100644 --- a/cli/tests/unit/websocket_test.ts +++ b/cli/tests/unit/websocket_test.ts @@ -36,3 +36,49 @@ Deno.test(async function websocketPingPong() { await promise; ws.close(); }); + +// https://github.com/denoland/deno/issues/18700 +Deno.test( + { sanitizeOps: false, sanitizeResources: false }, + async function websocketWriteLock() { + const ac = new AbortController(); + const listeningPromise = deferred(); + + const server = Deno.serve({ + handler: (req) => { + const { socket, response } = Deno.upgradeWebSocket(req); + socket.onopen = function () { + setTimeout(() => socket.send("Hello"), 500); + }; + socket.onmessage = function (e) { + assertEquals(e.data, "Hello"); + ac.abort(); + }; + return response; + }, + signal: ac.signal, + onListen: () => listeningPromise.resolve(), + hostname: "localhost", + port: 4246, + }); + + await listeningPromise; + const promise = deferred(); + const ws = new WebSocket("ws://localhost:4246/"); + assertEquals(ws.url, "ws://localhost:4246/"); + ws.onerror = () => fail(); + ws.onmessage = (e) => { + assertEquals(e.data, "Hello"); + setTimeout(() => { + ws.send(e.data); + }, 1000); + promise.resolve(); + }; + ws.onclose = () => { + promise.resolve(); + }; + + await Promise.all([promise, server]); + ws.close(); + }, +); -- cgit v1.2.3