Skip to content

Commit

Permalink
Prevent close() from blocking when reading is paused.
Browse files Browse the repository at this point in the history
Fix #1555.
  • Loading branch information
aaugustin committed Jan 11, 2025
1 parent 6317c00 commit 031ec31
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 6 deletions.
11 changes: 7 additions & 4 deletions docs/project/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ notice.
Bug fixes
.........

* Fixed ``connection.recv(timeout=0)`` in the :mod:`threading` implementation.
If a message is already received, it is returned. Previously,
:exc:`TimeoutError` was raised incorrectly.

* Wrapped errors when reading the opening handshake request or response in
:exc:`~exceptions.InvalidMessage` so that :func:`~asyncio.client.connect`
raises :exc:`~exceptions.InvalidHandshake` or a subclass when the opening
handshake fails.

* Fixed :meth:`~sync.connection.Connection.recv` with ``timeout=0`` in the
:mod:`threading` implementation. If a message is already received, it is
returned. Previously, :exc:`TimeoutError` was raised incorrectly.

* Prevented :meth:`~sync.connection.Connection.close` from blocking when
receive buffers are saturated in the :mod:`threading` implementation.

.. _14.1:

14.1
Expand Down
2 changes: 1 addition & 1 deletion src/websockets/asyncio/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def close(self) -> None:
"""
End the stream of frames.
Callling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`,
Calling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`,
or :meth:`put` is safe. They will raise :exc:`EOFError`.
"""
Expand Down
7 changes: 6 additions & 1 deletion src/websockets/sync/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def close(self) -> None:
"""
End the stream of frames.
Callling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`,
Calling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`,
or :meth:`put` is safe. They will raise :exc:`EOFError`.
"""
Expand All @@ -311,3 +311,8 @@ def close(self) -> None:
if self.get_in_progress:
# Unblock get() or get_iter().
self.frames.put(None)

if self.paused:
# Unblock recv_events().
self.paused = False
self.resume()
12 changes: 12 additions & 0 deletions tests/sync/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,18 @@ def test_put_fails_after_close(self):
with self.assertRaises(EOFError):
self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9"))

def test_close_resumes_reading(self):
"""close unblocks reading when queue is above the high-water mark."""
self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9"))
self.assembler.put(Frame(OP_TEXT, b"more caf\xc3\xa9"))
self.assembler.put(Frame(OP_TEXT, b"water"))

# queue is at the high-water mark
assert self.assembler.paused

self.assembler.close()
self.resume.assert_called_once_with()

def test_close_is_idempotent(self):
"""close can be called multiple times safely."""
self.assembler.close()
Expand Down

0 comments on commit 031ec31

Please sign in to comment.