Skip to content

Commit ed5531c

Browse files
miss-islingtonasvetlovclaude
authored
[3.14] gh-109564: asyncio: drop connections accepted by a closed server (GH-160851) (#160879)
gh-109564: asyncio: drop connections accepted by a closed server (GH-160851) If Server.close() ran after a connection was accepted but before its transport was created, Server._attach() hit an assertion, the error was reported to the exception handler, and the half-built transport later raised TypeError from __del__. Close such connections quietly instead, in both the selector and proactor accept paths. (cherry picked from commit 5364bdc) Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 00aa821 commit ed5531c

4 files changed

Lines changed: 45 additions & 0 deletions

File tree

‎Lib/asyncio/proactor_events.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,11 @@ def loop(f=None):
874874
logger.debug("%r got a new connection from %r: %r",
875875
server, addr, conn)
876876
protocol = protocol_factory()
877+
if server is not None and not server.is_serving():
878+
# The server was closed after the connection was
879+
# accepted.
880+
conn.close()
881+
return
877882
if sslcontext is not None:
878883
self._make_ssl_transport(
879884
conn, protocol, sslcontext, server_side=True,

‎Lib/asyncio/selector_events.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ async def _accept_connection2(
223223
transport = None
224224
try:
225225
protocol = protocol_factory()
226+
if server is not None and not server.is_serving():
227+
# The server was closed after the connection was accepted.
228+
conn.close()
229+
return
226230
waiter = self.create_future()
227231
if sslcontext:
228232
transport = self._make_ssl_transport(

‎Lib/test/test_asyncio/test_server.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import threading
66
import unittest
77

8+
from test import support
89
from test.support import socket_helper
910
from test.test_asyncio import utils as test_utils
1011
from test.test_asyncio import functional as func_tests
@@ -187,6 +188,37 @@ async def serve(rd, wr):
187188
loop.call_soon(wr.close)
188189
await srv.wait_closed()
189190

191+
async def test_close_before_transport_created(self):
192+
# gh-109564: Closing the server after a connection was accepted,
193+
# but before its transport was created, must drop the connection
194+
# quietly.
195+
connected = False
196+
197+
class Proto(asyncio.Protocol):
198+
def connection_made(self, transport):
199+
nonlocal connected
200+
connected = True
201+
202+
def factory():
203+
srv.close()
204+
return Proto()
205+
206+
errors = []
207+
loop = asyncio.get_running_loop()
208+
loop.set_exception_handler(lambda loop, ctx: errors.append(ctx))
209+
srv = await loop.create_server(factory, socket_helper.HOSTv4, 0)
210+
self.addCleanup(srv.close)
211+
212+
addr = srv.sockets[0].getsockname()
213+
(rd, wr) = await asyncio.open_connection(addr[0], addr[1])
214+
self.addCleanup(wr.close)
215+
async with asyncio.timeout(support.SHORT_TIMEOUT):
216+
self.assertEqual(await rd.read(), b'')
217+
await srv.wait_closed()
218+
219+
self.assertFalse(connected)
220+
self.assertEqual(errors, [])
221+
190222
async def test_close_clients(self):
191223
async def serve(rd, wr):
192224
try:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a race in :mod:`asyncio` where closing a server after it accepted a
2+
connection, but before the connection's transport was created, reported an
3+
:exc:`AssertionError` to the exception handler. Such connections are now
4+
closed quietly.

0 commit comments

Comments
 (0)