RequestServer: Don't create already-expired WebSockets in DNS callback

This prevents a race condition:
1. Try to connect a websocket
2. DNS lookup starts
3. JS causes the websocket to no longer be alive, and it is GCed
4. websocket_close() is called, but it doesn't find a websocket with
   that websocket_id, so nothing happens
5. DNS lookup completes, and opens the websocket
6. This websocket never gets closed

By separately tracking which websockets we are trying to connect, we can
record the fact we tried to close it, and then the DNS lookup callback
can skip creating the now-unwanted websocket.
This commit is contained in:
Sam Atkins
2026-03-31 11:39:59 +01:00
parent 5b29382497
commit 22d7138c8d
Notes: github-actions[bot] 2026-04-01 18:38:05 +00:00
2 changed files with 7 additions and 0 deletions

View File

@@ -355,6 +355,7 @@ void ConnectionFromClient::remove_cache_entries_accessed_since(UnixDateTime sinc
void ConnectionFromClient::websocket_connect(u64 websocket_id, URL::URL url, ByteString origin, Vector<ByteString> protocols, Vector<ByteString> extensions, Vector<HTTP::Header> additional_request_headers)
{
auto host = url.serialized_host().to_byte_string();
m_pending_websockets.set(websocket_id);
m_resolver->dns.lookup(host, DNS::Messages::Class::IN, { DNS::Messages::ResourceType::A, DNS::Messages::ResourceType::AAAA })
->when_rejected([this, websocket_id](auto const& error) {
@@ -368,6 +369,10 @@ void ConnectionFromClient::websocket_connect(u64 websocket_id, URL::URL url, Byt
return;
}
// Don't connect the websocket if we already requested to close it before the DNS lookup completed.
if (!m_pending_websockets.remove(websocket_id))
return;
WebSocket::ConnectionInfo connection_info(move(url));
connection_info.set_origin(move(origin));
connection_info.set_protocols(move(protocols));
@@ -410,6 +415,7 @@ void ConnectionFromClient::websocket_send(u64 websocket_id, bool is_text, ByteBu
void ConnectionFromClient::websocket_close(u64 websocket_id, u16 code, ByteString reason)
{
m_pending_websockets.remove(websocket_id);
if (auto* connection = m_websockets.get(websocket_id).value_or({}); connection && connection->ready_state() == WebSocket::ReadyState::Open)
connection->close(code, reason);
}

View File

@@ -80,6 +80,7 @@ private:
HashMap<u64, NonnullOwnPtr<Request>> m_active_requests;
HashMap<u64, NonnullOwnPtr<Request>> m_active_revalidation_requests;
HashTable<u64> m_pending_websockets;
HashMap<u64, RefPtr<WebSocket::WebSocket>> m_websockets;
RefPtr<Core::Timer> m_timer;