diff options
author | Guido van Rossum <guido@python.org> | 2023-10-28 18:04:29 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-28 18:04:29 (GMT) |
commit | 26553695592ad399f735d4dbaf32fd871d0bb1e1 (patch) | |
tree | 7f777a94e61f9cdc97a961f235019034f0411b3c /Lib/asyncio | |
parent | 9d4a1a480b65196c3aabbcd2d165d1fb86d0c8e5 (diff) | |
download | cpython-26553695592ad399f735d4dbaf32fd871d0bb1e1.zip cpython-26553695592ad399f735d4dbaf32fd871d0bb1e1.tar.gz cpython-26553695592ad399f735d4dbaf32fd871d0bb1e1.tar.bz2 |
gh-79033: Try to fix asyncio.Server.wait_closed() again (GH-111336)
* Try to fix asyncio.Server.wait_closed() again
I identified the condition that `wait_closed()` is intended
to wait for: the server is closed *and* there are no more
active connections.
When this condition first becomes true, `_wakeup()` is called
(either from `close()` or from `_detach()`) and it sets `_waiters`
to `None`. So we just check for `self._waiters is None`; if it's
not `None`, we know we have to wait, and do so.
A problem was that the new test introduced in 3.12 explicitly
tested that `wait_closed()` returns immediately when the server
is *not* closed but there are currently no active connections.
This was a mistake (probably a misunderstanding of the intended
semantics). I've fixed the test, and added a separate test that
checks exactly for this scenario.
I also fixed an oddity where in `_wakeup()` the result of the
waiter was set to the waiter itself. This result is not used
anywhere and I changed this to `None`, to avoid a GC cycle.
* Update Lib/asyncio/base_events.py
---------
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
Diffstat (limited to 'Lib/asyncio')
-rw-r--r-- | Lib/asyncio/base_events.py | 24 |
1 files changed, 22 insertions, 2 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 0476de6..416c732 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -305,7 +305,7 @@ class Server(events.AbstractServer): self._waiters = None for waiter in waiters: if not waiter.done(): - waiter.set_result(waiter) + waiter.set_result(None) def _start_serving(self): if self._serving: @@ -377,7 +377,27 @@ class Server(events.AbstractServer): self._serving_forever_fut = None async def wait_closed(self): - if self._waiters is None or self._active_count == 0: + """Wait until server is closed and all connections are dropped. + + - If the server is not closed, wait. + - If it is closed, but there are still active connections, wait. + + Anyone waiting here will be unblocked once both conditions + (server is closed and all connections have been dropped) + have become true, in either order. + + Historical note: In 3.11 and before, this was broken, returning + immediately if the server was already closed, even if there + were still active connections. An attempted fix in 3.12.0 was + still broken, returning immediately if the server was still + open and there were no active connections. Hopefully in 3.12.1 + we have it right. + """ + # Waiters are unblocked by self._wakeup(), which is called + # from two places: self.close() and self._detach(), but only + # when both conditions have become true. To signal that this + # has happened, self._wakeup() sets self._waiters to None. + if self._waiters is None: return waiter = self._loop.create_future() self._waiters.append(waiter) |