Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/project/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ notice.

*In development*

New features
............

* Added :meth:`~sync.connection.Connection.wait_closed` to the :mod:`threading`
implementation.

.. _17.1:

17.1
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/sync/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Using a connection

.. automethod:: close

.. automethod:: wait_closed

.. automethod:: ping

.. automethod:: pong
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/sync/connection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Both sides (:mod:`threading`)

.. automethod:: close

.. automethod:: wait_closed

.. automethod:: ping

.. automethod:: pong
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/sync/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Using a connection

.. automethod:: close

.. automethod:: wait_closed

.. automethod:: ping

.. automethod:: pong
Expand Down
20 changes: 20 additions & 0 deletions src/websockets/sync/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,26 @@ def close(
# They mean that the connection is closed, which was the goal.
pass

def wait_closed(self, timeout: float | None = None) -> bool:
"""
Wait until the connection is closed.

:meth:`wait_closed` waits for the closing handshake to complete and for
the TCP connection to terminate.

If a timeout is provided and elapses before the connection is closed,
:meth:`wait_closed` returns :obj:`False`. Else, it returns :obj:`True`.

Args:
timeout: Optional timeout in seconds.

Returns:
Whether the connection is closed.

"""
self.recv_events_thread.join(timeout)
return not self.recv_events_thread.is_alive()

def ping(
self,
data: DataLike | None = None,
Expand Down
8 changes: 4 additions & 4 deletions tests/asyncio/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,11 @@ async def fragments():

async def test_wait_closed(self):
"""wait_closed waits for the connection to close."""
wait_closed_task = asyncio.create_task(self.connection.wait_closed())
await asyncio.sleep(0) # let the event loop start wait_closed_task
self.assertFalse(wait_closed_task.done())
waiter_task = asyncio.create_task(self.connection.wait_closed())
await asyncio.sleep(0) # let the event loop start waiter_task
self.assertFalse(waiter_task.done())
await self.connection.close()
self.assertTrue(wait_closed_task.done())
self.assertTrue(waiter_task.done())

# Test ping.

Expand Down
29 changes: 29 additions & 0 deletions tests/sync/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,35 @@ def fragments():
)
self.assertIsNone(exc.__cause__)

# Test wait_closed.

def test_wait_closed(self):
"""wait_closed waits for the connection to close."""
closed = threading.Event()

def waiter():
self.connection.wait_closed()
closed.set()

with self.run_in_thread(waiter):
self.assertFalse(closed.wait(MS))
self.connection.close()
self.assertTrue(closed.wait(MS))

def test_wait_closed_no_timeout(self):
"""wait_closed without a timeout returns True."""
self.connection.close()
self.assertTrue(self.connection.wait_closed())

def test_wait_closed_with_timeout(self):
"""wait_closed returns True when the connection closes."""
self.connection.close()
self.assertTrue(self.connection.wait_closed(timeout=MS))

def test_wait_closed_with_timeout_elapsed(self):
"""wait_closed returns False when the timeout elapses."""
self.assertFalse(self.connection.wait_closed(timeout=MS))

# Test ping.

@patch("random.getrandbits")
Expand Down
4 changes: 2 additions & 2 deletions tests/trio/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,11 +863,11 @@ async def test_wait_closed(self):
"""wait_closed waits for the connection to close."""
closed = trio.Event()

async def closer():
async def waiter():
await self.connection.wait_closed()
closed.set()

self.nursery.start_soon(closer)
self.nursery.start_soon(waiter)
await trio.testing.wait_all_tasks_blocked()
self.assertFalse(closed.is_set())

Expand Down