Skip to content

Commit c643223

Browse files
committed
gh-156400: Close the socket or pipe when transport creation fails in asyncio datagram/pipe endpoints
1 parent e2118b0 commit c643223

3 files changed

Lines changed: 68 additions & 10 deletions

File tree

Lib/asyncio/base_events.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,10 +1497,15 @@ async def create_datagram_endpoint(self, protocol_factory,
14971497
else:
14981498
raise exceptions[0]
14991499

1500-
protocol = protocol_factory()
1501-
waiter = self.create_future()
1502-
transport = self._make_datagram_transport(
1503-
sock, protocol, r_addr, waiter)
1500+
try:
1501+
protocol = protocol_factory()
1502+
waiter = self.create_future()
1503+
transport = self._make_datagram_transport(
1504+
sock, protocol, r_addr, waiter)
1505+
except:
1506+
# gh-156400: close the socket if the transport is never created.
1507+
sock.close()
1508+
raise
15041509
if self._debug:
15051510
if local_addr:
15061511
logger.info("Datagram endpoint local_addr=%r remote_addr=%r "
@@ -1714,9 +1719,14 @@ async def connect_accepted_socket(
17141719
return transport, protocol
17151720

17161721
async def connect_read_pipe(self, protocol_factory, pipe):
1717-
protocol = protocol_factory()
1718-
waiter = self.create_future()
1719-
transport = self._make_read_pipe_transport(pipe, protocol, waiter)
1722+
try:
1723+
protocol = protocol_factory()
1724+
waiter = self.create_future()
1725+
transport = self._make_read_pipe_transport(pipe, protocol, waiter)
1726+
except:
1727+
# gh-156400: close the pipe if the transport is never created.
1728+
pipe.close()
1729+
raise
17201730

17211731
try:
17221732
await waiter
@@ -1730,9 +1740,14 @@ async def connect_read_pipe(self, protocol_factory, pipe):
17301740
return transport, protocol
17311741

17321742
async def connect_write_pipe(self, protocol_factory, pipe):
1733-
protocol = protocol_factory()
1734-
waiter = self.create_future()
1735-
transport = self._make_write_pipe_transport(pipe, protocol, waiter)
1743+
try:
1744+
protocol = protocol_factory()
1745+
waiter = self.create_future()
1746+
transport = self._make_write_pipe_transport(pipe, protocol, waiter)
1747+
except:
1748+
# gh-156400: close the pipe if the transport is never created.
1749+
pipe.close()
1750+
raise
17361751

17371752
try:
17381753
await waiter

Lib/test/test_asyncio/test_base_events.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2041,6 +2041,43 @@ def test_create_datagram_endpoint_sock(self):
20412041
self.loop.run_until_complete(protocol.done)
20422042
self.assertEqual('CLOSED', protocol.state)
20432043

2044+
def test_create_datagram_endpoint_transport_error_closes_sock(self):
2045+
# gh-156400: the socket is closed if the transport is never created.
2046+
sock = mock.Mock()
2047+
sock.type = socket.SOCK_DGRAM
2048+
2049+
def factory():
2050+
raise ZeroDivisionError
2051+
2052+
coro = self.loop.create_datagram_endpoint(factory, sock=sock)
2053+
with self.assertRaises(ZeroDivisionError):
2054+
self.loop.run_until_complete(coro)
2055+
self.assertTrue(sock.close.called)
2056+
2057+
def test_connect_read_pipe_transport_error_closes_pipe(self):
2058+
# gh-156400: the pipe is closed if the transport is never created.
2059+
pipe = mock.Mock()
2060+
2061+
def factory():
2062+
raise ZeroDivisionError
2063+
2064+
coro = self.loop.connect_read_pipe(factory, pipe)
2065+
with self.assertRaises(ZeroDivisionError):
2066+
self.loop.run_until_complete(coro)
2067+
self.assertTrue(pipe.close.called)
2068+
2069+
def test_connect_write_pipe_transport_error_closes_pipe(self):
2070+
# gh-156400: the pipe is closed if the transport is never created.
2071+
pipe = mock.Mock()
2072+
2073+
def factory():
2074+
raise ZeroDivisionError
2075+
2076+
coro = self.loop.connect_write_pipe(factory, pipe)
2077+
with self.assertRaises(ZeroDivisionError):
2078+
self.loop.run_until_complete(coro)
2079+
self.assertTrue(pipe.close.called)
2080+
20442081
@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
20452082
def test_create_datagram_endpoint_sock_unix(self):
20462083
fut = self.loop.create_datagram_endpoint(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix socket and pipe leaks in :mod:`asyncio` when ``protocol_factory()`` or
2+
transport creation fails in :meth:`loop.create_datagram_endpoint
3+
<asyncio.loop.create_datagram_endpoint>`, :meth:`loop.connect_read_pipe
4+
<asyncio.loop.connect_read_pipe>`, and :meth:`loop.connect_write_pipe
5+
<asyncio.loop.connect_write_pipe>`. The socket or pipe is now closed instead
6+
of leaking until garbage collection.

0 commit comments

Comments
 (0)