The sockets that represent existing incoming client connections
are left open.
- The server is closed asynchronously, use the :meth:`wait_closed`
- coroutine to wait until the server is closed.
+ The server is closed asynchronously; use the :meth:`wait_closed`
+ coroutine to wait until the server is closed (and no more
+ connections are active).
.. method:: get_loop()
.. coroutinemethod:: wait_closed()
- Wait until the :meth:`close` method completes.
+ Wait until the :meth:`close` method completes and all active
+ connections have finished.
.. attribute:: sockets
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:
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)
class TestServer2(unittest.IsolatedAsyncioTestCase):
- async def test_wait_closed(self):
+ async def test_wait_closed_basic(self):
async def serve(*args):
pass
srv = await asyncio.start_server(serve, socket_helper.HOSTv4, 0)
+ self.addCleanup(srv.close)
- # active count = 0
+ # active count = 0, not closed: should block
task1 = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
- self.assertTrue(task1.done())
+ self.assertFalse(task1.done())
- # active count != 0
+ # active count != 0, not closed: should block
srv._attach()
task2 = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
+ self.assertFalse(task1.done())
self.assertFalse(task2.done())
srv.close()
await asyncio.sleep(0)
+ # active count != 0, closed: should block
+ task3 = asyncio.create_task(srv.wait_closed())
+ await asyncio.sleep(0)
+ self.assertFalse(task1.done())
self.assertFalse(task2.done())
+ self.assertFalse(task3.done())
srv._detach()
+ # active count == 0, closed: should unblock
+ await task1
await task2
+ await task3
+ await srv.wait_closed() # Return immediately
+
+ async def test_wait_closed_race(self):
+ # Test a regression in 3.12.0, should be fixed in 3.12.1
+ async def serve(*args):
+ pass
+
+ srv = await asyncio.start_server(serve, socket_helper.HOSTv4, 0)
+ self.addCleanup(srv.close)
+
+ task = asyncio.create_task(srv.wait_closed())
+ await asyncio.sleep(0)
+ self.assertFalse(task.done())
+ srv._attach()
+ loop = asyncio.get_running_loop()
+ loop.call_soon(srv.close)
+ loop.call_soon(srv._detach)
+ await srv.wait_closed()
+
+
@unittest.skipUnless(hasattr(asyncio, 'ProactorEventLoop'), 'Windows only')
--- /dev/null
+Another attempt at fixing :func:`asyncio.Server.wait_closed()`. It now
+blocks until both conditions are true: the server is closed, *and* there
+are no more active connections. (This means that in some cases where in
+3.12.0 this function would *incorrectly* have returned immediately,
+it will now block; in particular, when there are no active connections
+but the server hasn't been closed yet.)