Skip to content

Commit 001c286

Browse files
feat: Add unit-test to capture unraised exception
1 parent 29c3232 commit 001c286

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

Lib/asyncio/selector_events.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,10 @@ async def _accept_connection2(
243243
# It's now up to the protocol to handle the connection.
244244

245245
except (SystemExit, KeyboardInterrupt):
246+
conn.close()
246247
raise
247248
except BaseException as exc:
249+
conn.close()
248250
if self._debug:
249251
context = {
250252
'message':

Lib/test/test_asyncio/test_server.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import asyncio
2+
import gc
23
import os
34
import socket
45
import time
56
import threading
67
import unittest
78

9+
from test import support
810
from test.support import socket_helper
11+
from test.support import warnings_helper
912
from test.test_asyncio import utils as test_utils
1013
from test.test_asyncio import functional as func_tests
1114

@@ -266,6 +269,49 @@ async def serve(rd, wr):
266269
await asyncio.sleep(0)
267270
self.assertTrue(task.done())
268271

272+
async def test_close_race(self):
273+
274+
srv = await asyncio.start_server(lambda *_: None, socket_helper.HOSTv4, 0)
275+
srv_sock = srv.sockets[0]
276+
addr = srv_sock.getsockname()
277+
278+
# When the server is closed before a connection is handled but after the
279+
# connection is accepted, then a race-condition exists between the handler
280+
# transport and the server, both which will attempt to wakeup the server to set
281+
# any server waiters. We can recreate race-condition by opening a connection and
282+
# waiting for the server reader callback before closing the server
283+
loop = asyncio.get_running_loop()
284+
srv_reader, _ = loop._selector.get_key(srv_sock.fileno()).data
285+
conn_task = asyncio.create_task(asyncio.open_connection(addr[0], addr[1]))
286+
for _ in range(10):
287+
await asyncio.sleep(0)
288+
if srv_reader in loop._ready:
289+
break
290+
291+
# Ensure accepted connection task is scheduled by the server reader, but not
292+
# completed, before closing the server.
293+
await asyncio.sleep(0)
294+
srv.close()
295+
296+
# Complete the client connection to close the socket. Suppress errors in the
297+
# handler transport due to failing to attach to closed server.
298+
with support.captured_stderr():
299+
try:
300+
_, wr = await conn_task
301+
self.addCleanup(wr.close)
302+
except OSError:
303+
pass
304+
305+
await srv.wait_closed()
306+
307+
# Verify the handler transport does not raise an error due to multiple calls to
308+
# the server wakeup. Suppress expected ResourceWarnings from the handler
309+
# transport failing to attach to the closed server.
310+
with warnings_helper.check_warnings(("unclosed transport", ResourceWarning)), \
311+
support.catch_unraisable_exception() as cm:
312+
support.gc_collect()
313+
self.assertIsNone(cm.unraisable)
314+
269315

270316
# Test the various corner cases of Unix server socket removal
271317
class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):

0 commit comments

Comments
 (0)