From c880a34541e26eaf5c65ba45fb47c6896dc6464b Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 17 Apr 2026 16:10:14 +0100 Subject: [PATCH] LibRequests: Fail websockets when RequestServer dies Detach live websocket wrappers from a dead RequestServer connection, notify them with error and close events, and defer the on_request_server_died callback out of RequestClient::die(). Without this, a dead RequestServer could leave existing websocket objects stuck forever and future websocket construction could keep using a stale RequestClient with no terminal events. --- Libraries/LibRequests/RequestClient.cpp | 22 ++++++++++++++++++++++ Libraries/LibRequests/WebSocket.cpp | 11 ++++++++++- Libraries/LibRequests/WebSocket.h | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Libraries/LibRequests/RequestClient.cpp b/Libraries/LibRequests/RequestClient.cpp index a509117f99..ce01156b03 100644 --- a/Libraries/LibRequests/RequestClient.cpp +++ b/Libraries/LibRequests/RequestClient.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -28,8 +29,29 @@ void RequestClient::die() for (auto& [id, promise] : m_pending_cache_size_estimations) promise->reject(Error::from_string_literal("RequestServer process died")); + auto websockets = move(m_websockets); + m_requests.clear(); m_pending_cache_size_estimations.clear(); + m_websockets.clear(); + + for (auto& [id, websocket] : websockets) { + auto ready_state = websocket->ready_state(); + websocket->detach_from_client({}); + websocket->set_ready_state(WebSocket::ReadyState::Closed); + + auto error = ready_state == WebSocket::ReadyState::Connecting + ? WebSocket::Error::CouldNotEstablishConnection + : WebSocket::Error::ServerClosedSocket; + websocket->did_error({}, to_underlying(error)); + websocket->did_close({}, to_underlying(::WebSocket::CloseStatusCode::AbnormalClosure), {}, false); + } + + if (auto request_server_died_callback = move(on_request_server_died)) { + Core::deferred_invoke([request_server_died_callback = move(request_server_died_callback)]() mutable { + request_server_died_callback(); + }); + } } RefPtr RequestClient::start_request(ByteString const& method, URL::URL const& url, Optional request_headers, ReadonlyBytes request_body, HTTP::CacheMode cache_mode, HTTP::Cookie::IncludeCredentials include_credentials, Core::ProxyData const& proxy_data) diff --git a/Libraries/LibRequests/WebSocket.cpp b/Libraries/LibRequests/WebSocket.cpp index ac574e522d..eb4a952aca 100644 --- a/Libraries/LibRequests/WebSocket.cpp +++ b/Libraries/LibRequests/WebSocket.cpp @@ -37,6 +37,8 @@ void WebSocket::set_subprotocol_in_use(ByteString subprotocol) void WebSocket::send(ReadonlyBytes binary_or_text_message, bool is_text) { + if (!m_client) + return; m_client->async_websocket_send(m_websocket_id, is_text, binary_or_text_message); } @@ -47,6 +49,8 @@ void WebSocket::send(StringView text_message) void WebSocket::close(u16 code, ByteString reason) { + if (!m_client) + return; m_client->async_websocket_close(m_websocket_id, code, move(reason)); } @@ -78,9 +82,14 @@ void WebSocket::did_request_certificates(Badge) { if (on_certificate_requested) { auto result = on_certificate_requested(); - if (!m_client->websocket_set_certificate(m_websocket_id, result.certificate, result.key)) + if (!m_client || !m_client->websocket_set_certificate(m_websocket_id, result.certificate, result.key)) dbgln("WebSocket: set_certificate failed"); } } +void WebSocket::detach_from_client(Badge) +{ + m_client = nullptr; +} + } diff --git a/Libraries/LibRequests/WebSocket.h b/Libraries/LibRequests/WebSocket.h index 057a4e0037..77e7a83751 100644 --- a/Libraries/LibRequests/WebSocket.h +++ b/Libraries/LibRequests/WebSocket.h @@ -70,6 +70,7 @@ public: void did_error(Badge, i32); void did_close(Badge, u16, ByteString, bool); void did_request_certificates(Badge); + void detach_from_client(Badge); private: WebSocket(RequestClient&, u64 websocket_id);