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.
This commit is contained in:
Sam Atkins 2026-04-17 16:10:14 +01:00 committed by Shannon Booth
parent 48a0e9fef7
commit c880a34541
3 changed files with 33 additions and 1 deletions

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/EventLoop.h>
#include <LibCore/Promise.h>
#include <LibCore/System.h>
#include <LibRequests/Request.h>
@ -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<Request> RequestClient::start_request(ByteString const& method, URL::URL const& url, Optional<HTTP::HeaderList const&> request_headers, ReadonlyBytes request_body, HTTP::CacheMode cache_mode, HTTP::Cookie::IncludeCredentials include_credentials, Core::ProxyData const& proxy_data)

View file

@ -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<RequestClient>)
{
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<RequestClient>)
{
m_client = nullptr;
}
}

View file

@ -70,6 +70,7 @@ public:
void did_error(Badge<RequestClient>, i32);
void did_close(Badge<RequestClient>, u16, ByteString, bool);
void did_request_certificates(Badge<RequestClient>);
void detach_from_client(Badge<RequestClient>);
private:
WebSocket(RequestClient&, u64 websocket_id);