8000 gh-135836: Fix IndexError in asyncio.create_connection() · python/cpython@2c7545f · GitHub
[go: up one dir, main page]

Skip to content

Commit 2c7545f

Browse files
gh-135836: Fix IndexError in asyncio.create_connection()
It occurs when non-OSError exception is raised during connection and socket's close() raises OSError.
1 parent 34393cb commit 2c7545f

File tree

3 files changed

+65
-30
lines changed

3 files changed

+65
-30
lines changed

Lib/asyncio/base_events.py

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,38 +1016,40 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
10161016
family, type_, proto, _, address = addr_info
10171017
sock = None
10181018
try:
1019-
sock = socket.socket(family=family, type=type_, proto=proto)
1020-
sock.setblocking(False)
1021-
if local_addr_infos is not None:
1022-
for lfamily, _, _, _, laddr in local_addr_infos:
1023-
# skip local addresses of different family
1024-
if lfamily != family:
1025-
continue
1026-
try:
1027-
sock.bind(laddr)
1028-
break
1029-
except OSError as exc:
1030-
msg = (
1031-
f'error while attempting to bind on '
1032-
f'address {laddr!r}: {str(exc).lower()}'
1033-
)
1034-
exc = OSError(exc.errno, msg)
1035-
my_exceptions.append(exc)
1036-
else: # all bind attempts failed
1037-
if my_exceptions:
1038-
raise my_exceptions.pop()
1039-
else:
1040-
raise OSError(f"no matching local address with {family=} found")
1041-
await self.sock_connect(sock, address)
1042-
return sock
1043-
except OSError as exc:
1044-
my_exceptions.append(exc)
1045-
if sock is not None:
1046-
sock.close()
1047-
raise
1019+
try:
1020+
sock = socket.socket(family=family, type=type_, proto=proto)
1021+
sock.setblocking(False)
1022+
if local_addr_infos is not None:
1023+
for lfamily, _, _, _, laddr in local_addr_infos:
1024+
# skip local addresses of different family
1025+
if lfamily != family:
1026+
continue
1027+
try:
1028+
sock.bind(laddr)
1029+
break
1030+
except OSError as exc:
1031+
msg = (
1032+
f'error while attempting to bind on '
1033+
f'address {laddr!r}: {str(exc).lower()}'
1034+
)
1035+
exc = OSError(exc.errno, msg)
1036+
my_exceptions.append(exc)
1037+
else: # all bind attempts failed
1038+
if my_exceptions:
1039+
raise my_exceptions.pop()
1040+
else:
1041+
raise OSError(f"no matching local address with {family=} found")
1042+
await self.sock_connect(sock, address)
1043+
return sock
1044+
except OSError as exc:
1045+
my_exceptions.append(exc)
1046+
raise
10481047
except:
10491048
if sock is not None:
1050-
sock.close()
1049+
try:
1050+
sock.close()
1051+
except OSError:
1052+
pass
10511053
raise
10521054
finally:
10531055
exceptions = my_exceptions = None

Lib/test/test_asyncio/test_base_events.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import concurrent.futures
44
import errno
55
import math
6+
import os
67
import platform
78
import socket
89
import sys
@@ -24,6 +25,10 @@
2425
MOCK_ANY = mock.ANY
2526

2627

28+
class CustomError(Exception):
29+
pass
30+
31+
2732
def tearDownModule():
2833
asyncio._set_event_loop_policy(None)
2934

@@ -1296,6 +1301,31 @@ def getaddrinfo_task(*args, **kwds):
12961301
self.assertEqual(len(cm.exception.exceptions), 1)
12971302
self.assertIsInstance(cm.exception.exceptions[0], OSError)
12981303

1304+
def test_create_connection_connect_non_os_err_close_err(self):
1305+
# Test the case when sock_connect() raises non-OSError exception
1306+
# and sock.close() raises OSError.
1307+
async def getaddrinfo(*args, **kw):
1308+
return [(2, 1, 6, '', ('107.6.106.82', 80))]
1309+
1310+
def getaddrinfo_task(*args, **kwds):
1311+
return self.loop.create_task(getaddrinfo(*args, **kwds))
1312+
1313+
async def sock_connect(sock, address):
1314+
# Force sock.close() to raise OSError.
1315+
os.close(sock.fileno())
1316+
raise CustomError
1317+
1318+
self.loop.getaddrinfo = getaddrinfo_task
1319+
self.loop.sock_connect = sock_connect
1320+
1321+
coro = self.loop.create_connection(MyProto, 'example.com', 80)
1322+
self.assertRaises(
1323+
CustomError, self.loop.run_until_complete, coro)
1324+
1325+
coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
1326+
self.assertRaises(
1327+
CustomError, self.loop.run_until_complete, coro)
1328+
12991329
def test_create_connection_multiple(self):
13001330
async def getaddrinfo(*args, **kw):
13011331
return [(2, 1, 6, '', ('0.0.0.1', 80)),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :exc:`IndexError` in :meth:`asyncio.loop.create_connection` that could
2+
occur when non-\ :exc:`OSError` exception is raised during connection and
3+
socket's ``close()`` raises :exc:`!OSError`.

0 commit comments

Comments
 (0)
0