From 7a2f8f40af801de9e09b4411aa4af3cc5a86139f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 1 Feb 2025 23:24:43 +0100 Subject: [PATCH] Start recv_events only after attributes are initialized. Else, a race condition could lead to accessing self.pong_waiters before it is defined. --- src/websockets/asyncio/connection.py | 16 ++++++++-------- src/websockets/sync/connection.py | 28 +++++++++++++++------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/websockets/asyncio/connection.py b/src/websockets/asyncio/connection.py index 79429923..1b51e479 100644 --- a/src/websockets/asyncio/connection.py +++ b/src/websockets/asyncio/connection.py @@ -101,14 +101,6 @@ def __init__( # Protect sending fragmented messages. self.fragmented_send_waiter: asyncio.Future[None] | None = None - # Exception raised while reading from the connection, to be chained to - # ConnectionClosed in order to show why the TCP connection dropped. - self.recv_exc: BaseException | None = None - - # Completed when the TCP connection is closed and the WebSocket - # connection state becomes CLOSED. - self.connection_lost_waiter: asyncio.Future[None] = self.loop.create_future() - # Mapping of ping IDs to pong waiters, in chronological order. self.pong_waiters: dict[bytes, tuple[asyncio.Future[float], float]] = {} @@ -128,6 +120,14 @@ def __init__( # Task that sends keepalive pings. None when ping_interval is None. self.keepalive_task: asyncio.Task[None] | None = None + # Exception raised while reading from the connection, to be chained to + # ConnectionClosed in order to show why the TCP connection dropped. + self.recv_exc: BaseException | None = None + + # Completed when the TCP connection is closed and the WebSocket + # connection state becomes CLOSED. + self.connection_lost_waiter: asyncio.Future[None] = self.loop.create_future() + # Adapted from asyncio.FlowControlMixin self.paused: bool = False self.drain_waiters: collections.deque[asyncio.Future[None]] = ( diff --git a/src/websockets/sync/connection.py b/src/websockets/sync/connection.py index 0c517cc6..8b9e0625 100644 --- a/src/websockets/sync/connection.py +++ b/src/websockets/sync/connection.py @@ -101,19 +101,6 @@ def __init__( # Whether we are busy sending a fragmented message. self.send_in_progress = False - # Exception raised in recv_events, to be chained to ConnectionClosed - # in the user thread in order to show why the TCP connection dropped. - self.recv_exc: BaseException | None = None - - # Receiving events from the socket. This thread is marked as daemon to - # allow creating a connection in a non-daemon thread and using it in a - # daemon thread. This mustn't prevent the interpreter from exiting. - self.recv_events_thread = threading.Thread( - target=self.recv_events, - daemon=True, - ) - self.recv_events_thread.start() - # Mapping of ping IDs to pong waiters, in chronological order. self.pong_waiters: dict[bytes, tuple[threading.Event, float, bool]] = {} @@ -133,6 +120,21 @@ def __init__( # Thread that sends keepalive pings. None when ping_interval is None. self.keepalive_thread: threading.Thread | None = None + # Exception raised in recv_events, to be chained to ConnectionClosed + # in the user thread in order to show why the TCP connection dropped. + self.recv_exc: BaseException | None = None + + # Receiving events from the socket. This thread is marked as daemon to + # allow creating a connection in a non-daemon thread and using it in a + # daemon thread. This mustn't prevent the interpreter from exiting. + self.recv_events_thread = threading.Thread( + target=self.recv_events, + daemon=True, + ) + + # Start recv_events only after all attributes are initialized. + self.recv_events_thread.start() + # Public attributes @property