Skip to content

Commit 7a2f8f4

Browse files
committed
Start recv_events only after attributes are initialized.
Else, a race condition could lead to accessing self.pong_waiters before it is defined.
1 parent d60255b commit 7a2f8f4

File tree

2 files changed

+23
-21
lines changed

2 files changed

+23
-21
lines changed

src/websockets/asyncio/connection.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,6 @@ def __init__(
101101
# Protect sending fragmented messages.
102102
self.fragmented_send_waiter: asyncio.Future[None] | None = None
103103

104-
# Exception raised while reading from the connection, to be chained to
105-
# ConnectionClosed in order to show why the TCP connection dropped.
106-
self.recv_exc: BaseException | None = None
107-
108-
# Completed when the TCP connection is closed and the WebSocket
109-
# connection state becomes CLOSED.
110-
self.connection_lost_waiter: asyncio.Future[None] = self.loop.create_future()
111-
112104
# Mapping of ping IDs to pong waiters, in chronological order.
113105
self.pong_waiters: dict[bytes, tuple[asyncio.Future[float], float]] = {}
114106

@@ -128,6 +120,14 @@ def __init__(
128120
# Task that sends keepalive pings. None when ping_interval is None.
129121
self.keepalive_task: asyncio.Task[None] | None = None
130122

123+
# Exception raised while reading from the connection, to be chained to
124+
# ConnectionClosed in order to show why the TCP connection dropped.
125+
self.recv_exc: BaseException | None = None
126+
127+
# Completed when the TCP connection is closed and the WebSocket
128+
# connection state becomes CLOSED.
129+
self.connection_lost_waiter: asyncio.Future[None] = self.loop.create_future()
130+
131131
# Adapted from asyncio.FlowControlMixin
132132
self.paused: bool = False
133133
self.drain_waiters: collections.deque[asyncio.Future[None]] = (

src/websockets/sync/connection.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,6 @@ def __init__(
101101
# Whether we are busy sending a fragmented message.
102102
self.send_in_progress = False
103103

104-
# Exception raised in recv_events, to be chained to ConnectionClosed
105-
# in the user thread in order to show why the TCP connection dropped.
106-
self.recv_exc: BaseException | None = None
107-
108-
# Receiving events from the socket. This thread is marked as daemon to
109-
# allow creating a connection in a non-daemon thread and using it in a
110-
# daemon thread. This mustn't prevent the interpreter from exiting.
111-
self.recv_events_thread = threading.Thread(
112-
target=self.recv_events,
113-
daemon=True,
114-
)
115-
self.recv_events_thread.start()
116-
117104
# Mapping of ping IDs to pong waiters, in chronological order.
118105
self.pong_waiters: dict[bytes, tuple[threading.Event, float, bool]] = {}
119106

@@ -133,6 +120,21 @@ def __init__(
133120
# Thread that sends keepalive pings. None when ping_interval is None.
134121
self.keepalive_thread: threading.Thread | None = None
135122

123+
# Exception raised in recv_events, to be chained to ConnectionClosed
124+
# in the user thread in order to show why the TCP connection dropped.
125+
self.recv_exc: BaseException | None = None
126+
127+
# Receiving events from the socket. This thread is marked as daemon to
128+
# allow creating a connection in a non-daemon thread and using it in a
129+
# daemon thread. This mustn't prevent the interpreter from exiting.
130+
self.recv_events_thread = threading.Thread(
131+
target=self.recv_events,
132+
daemon=True,
133+
)
134+
135+
# Start recv_events only after all attributes are initialized.
136+
self.recv_events_thread.start()
137+
136138
# Public attributes
137139

138140
@property

0 commit comments

Comments
 (0)