From 88360ec56f8cf61e3f7fb879352864f7a68f7a5b Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:49:48 -0700 Subject: [PATCH] Fix rejected handshake cleanup in threading server Return explicitly when a process hook rejects the opening handshake instead of relying on an assertion that is swallowed or removed under optimization. Fixes #1722. --- docs/project/changelog.rst | 6 ++++++ src/websockets/sync/server.py | 5 ++++- tests/sync/test_server.py | 34 ++++++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/project/changelog.rst b/docs/project/changelog.rst index b259d12d8..b40c6c366 100644 --- a/docs/project/changelog.rst +++ b/docs/project/changelog.rst @@ -59,6 +59,12 @@ New features * Added the ``--insecure`` option to the ``websockets`` CLI to disable TLS certificate validation. +Bug fixes +......... + +* Avoided an internal assertion when the :mod:`threading` server rejects an + opening handshake. (:issue:`1722`) + .. _16.1: 16.1 diff --git a/src/websockets/sync/server.py b/src/websockets/sync/server.py index 480a8fc32..be66d1189 100644 --- a/src/websockets/sync/server.py +++ b/src/websockets/sync/server.py @@ -615,7 +615,10 @@ def protocol_select_subprotocol( connection.recv_events_thread.join() return - assert connection.protocol.state is OPEN + if connection.protocol.state is not OPEN: + connection.close_socket() + connection.recv_events_thread.join() + return try: connection.start_keepalive() handler(connection) diff --git a/tests/sync/test_server.py b/tests/sync/test_server.py index d04d1859a..325586b87 100644 --- a/tests/sync/test_server.py +++ b/tests/sync/test_server.py @@ -3,6 +3,7 @@ import http import logging import socket +import threading import time import unittest @@ -145,20 +146,37 @@ def process_request(ws, request): def test_process_request_returns_response(self): """Server aborts handshake if process_request returns a response.""" + assertions = [] + + def trace(frame, event, arg): + if ( + event == "exception" + and arg[0] is AssertionError + and frame.f_code.co_name == "conn_handler" + ): + assertions.append(arg[1]) + return trace + def process_request(ws, request): return ws.respond(http.HTTPStatus.FORBIDDEN, "Forbidden") def handler(ws): self.fail("handler must not run") - with run_server(handler, process_request=process_request) as server: - with self.assertRaises(InvalidStatus) as raised: - with connect(get_uri(server)): - self.fail("did not raise") - self.assertEqual( - str(raised.exception), - "server rejected WebSocket connection: HTTP 403", - ) + threading.settrace(trace) + try: + with run_server(handler, process_request=process_request) as server: + with self.assertRaises(InvalidStatus) as raised: + with connect(get_uri(server)): + self.fail("did not raise") + self.assertEqual( + str(raised.exception), + "server rejected WebSocket connection: HTTP 403", + ) + finally: + threading.settrace(None) + + self.assertEqual(assertions, []) def test_process_request_raises_exception(self): """Server returns an error if process_request raises an exception."""