Skip to content

Commit 95e32ba

Browse files
authored
gh-155941: Close the transport when client_connected_cb raises in asyncio (#155942)
1 parent 05ab13e commit 95e32ba

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

Lib/asyncio/streams.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,17 @@ def connection_made(self, transport):
239239
self._over_ssl = transport.get_extra_info('sslcontext') is not None
240240
if self._client_connected_cb is not None:
241241
writer = StreamWriter(transport, self, reader, self._loop)
242-
res = self._client_connected_cb(reader, writer)
242+
try:
243+
res = self._client_connected_cb(reader, writer)
244+
except Exception as exc:
245+
self._loop.call_exception_handler({
246+
'message': 'Unhandled exception in client_connected_cb',
247+
'exception': exc,
248+
'transport': transport,
249+
})
250+
transport.close()
251+
self._strong_reader = None
252+
return
243253
if coroutines.iscoroutine(res):
244254
def callback(task):
245255
if task.cancelled():

Lib/test/test_asyncio/test_streams.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,38 @@ async def handle_echo(reader, writer):
12671267
messages = self._basetest_unhandled_exceptions(handle_echo)
12681268
self.assertEqual(messages, [])
12691269

1270+
def test_unhandled_exception_sync_callback(self):
1271+
# An exception raised by a plain-function client_connected_cb is
1272+
# reported like the coroutine case and the transport is closed.
1273+
port = socket_helper.find_unused_port()
1274+
1275+
messages = []
1276+
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
1277+
1278+
async def client():
1279+
rd, wr = await asyncio.open_connection('localhost', port)
1280+
async with asyncio.timeout(60):
1281+
data = await rd.read()
1282+
self.assertEqual(data, b'') # the server closed the connection
1283+
wr.close()
1284+
await wr.wait_closed()
1285+
1286+
async def main():
1287+
def handle_echo(reader, writer):
1288+
raise Exception('test')
1289+
1290+
server = await asyncio.start_server(
1291+
handle_echo, 'localhost', port)
1292+
await server.start_serving()
1293+
await client()
1294+
server.close()
1295+
await server.wait_closed()
1296+
1297+
self.loop.run_until_complete(main())
1298+
1299+
self.assertEqual(messages[0]['message'],
1300+
'Unhandled exception in client_connected_cb')
1301+
12701302
def test_open_connection_happy_eyeball_refcycles(self):
12711303
port = socket_helper.find_unused_port()
12721304
async def main():
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`asyncio.start_server` when a plain-function *client_connected_cb*
2+
raises: the error is now reported like the coroutine case and the transport
3+
is closed, instead of leaving the connection open forever (which also made
4+
:meth:`asyncio.Server.wait_closed` hang).

0 commit comments

Comments
 (0)