Skip to content

Commit 788c329

Browse files
authored
gh-123720: When closing an asyncio server, stop the handlers (#124689)
1 parent 31c41a6 commit 788c329

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

Lib/asyncio/base_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ async def serve_forever(self):
381381
except exceptions.CancelledError:
382382
try:
383383
self.close()
384+
self.close_clients()
384385
await self.wait_closed()
385386
finally:
386387
raise

Lib/test/test_asyncio/test_server.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,38 @@ async def serve(rd, wr):
266266
await asyncio.sleep(0)
267267
self.assertTrue(task.done())
268268

269+
async def test_close_with_hanging_client(self):
270+
# Synchronize server cancellation only after the socket connection is
271+
# accepted and this event is set
272+
conn_event = asyncio.Event()
273+
class Proto(asyncio.Protocol):
274+
def connection_made(self, transport):
275+
conn_event.set()
276+
277+
loop = asyncio.get_running_loop()
278+
srv = await loop.create_server(Proto, socket_helper.HOSTv4, 0)
279+
280+
# Start the server
281+
serve_forever_task = asyncio.create_task(srv.serve_forever())
282+
await asyncio.sleep(0)
283+
284+
# Create a connection to server
285+
addr = srv.sockets[0].getsockname()
286+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
287+
sock.connect(addr)
288+
self.addCleanup(sock.close)
289+
290+
# Send a CancelledError into the server to emulate a Ctrl+C
291+
# KeyboardInterrupt whilst the server is handling a hanging client
292+
await conn_event.wait()
293+
serve_forever_task.cancel()
294+
295+
# Ensure the client is closed within a timeout
296+
async with asyncio.timeout(0.5):
297+
await srv.wait_closed()
298+
299+
self.assertFalse(srv.is_serving())
300+
269301

270302
# Test the various corner cases of Unix server socket removal
271303
class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
asyncio: Fix :func:`asyncio.Server.serve_forever` shutdown regression. Since
2+
3.12, cancelling ``serve_forever()`` could hang waiting for a handler blocked
3+
on a read from a client that never closed (effectively requiring two
4+
interrupts to stop); the shutdown sequence now ensures client streams are
5+
closed so ``serve_forever()`` exits promptly and handlers observe EOF.

0 commit comments

Comments
 (0)