summaryrefslogtreecommitdiffstats
path: root/Lib/asyncio
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2023-10-28 18:04:29 (GMT)
committerGitHub <noreply@github.com>2023-10-28 18:04:29 (GMT)
commit26553695592ad399f735d4dbaf32fd871d0bb1e1 (patch)
tree7f777a94e61f9cdc97a961f235019034f0411b3c /Lib/asyncio
parent9d4a1a480b65196c3aabbcd2d165d1fb86d0c8e5 (diff)
downloadcpython-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.py24
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)