Skip to content

Commit e7a098e

Browse files
committed
Prevent AssertionError in the recv_events thread.
close_socket() was interacting with the protocol, namely calling protocol.receive_of(), without locking the mutex. This created the possibility of a race condition. If two threads called receive_eof() concurrently, the second one could return before the first one finished running it. This led to receive_eof() returning (in the second thread) before the connection state was CLOSED, breaking an invariant. This race condition could be triggered reliably by shutting down the network (e.g., turning wifi off), closing the connection, and waiting for the timeout. Then, close() calls close_socket() — this happens in the `raise_close_exc` branch of send_context(). This unblocks the read in recv_events() which calls close_socket() in the `finally:` branch. Fix #1558.
1 parent 031ec31 commit e7a098e

File tree

2 files changed

+5
-3
lines changed

2 files changed

+5
-3
lines changed

src/websockets/sync/connection.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -923,8 +923,9 @@ def close_socket(self) -> None:
923923

924924
# Calling protocol.receive_eof() is safe because it's idempotent.
925925
# This guarantees that the protocol state becomes CLOSED.
926-
self.protocol.receive_eof()
927-
assert self.protocol.state is CLOSED
926+
with self.protocol_mutex:
927+
self.protocol.receive_eof()
928+
assert self.protocol.state is CLOSED
928929

929930
# Abort recv() with a ConnectionClosed exception.
930931
self.recv_messages.close()

tests/sync/test_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ def test_connection_closed_during_handshake(self):
151151
"""Client reads EOF before receiving handshake response from server."""
152152

153153
def close_connection(self, request):
154-
self.close_socket()
154+
self.socket.shutdown(socket.SHUT_RDWR)
155+
self.socket.close()
155156

156157
with run_server(process_request=close_connection) as server:
157158
with self.assertRaises(InvalidMessage) as raised:

0 commit comments

Comments
 (0)