Skip to content

Commit ba4a079

Browse files
authored
gh-156400: Close the socket or pipe when transport creation fails in asyncio datagram/pipe endpoints (#156401)
1 parent e620377 commit ba4a079

3 files changed

Lines changed: 61 additions & 3 deletions

File tree

Lib/asyncio/base_events.py

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

1500-
protocol = protocol_factory()
1500+
try:
1501+
protocol = protocol_factory()
1502+
except:
1503+
# gh-156400: no transport owns the socket yet, so close it.
1504+
sock.close()
1505+
raise
15011506
waiter = self.create_future()
15021507
transport = self._make_datagram_transport(
15031508
sock, protocol, r_addr, waiter)
@@ -1714,7 +1719,12 @@ async def connect_accepted_socket(
17141719
return transport, protocol
17151720

17161721
async def connect_read_pipe(self, protocol_factory, pipe):
1717-
protocol = protocol_factory()
1722+
try:
1723+
protocol = protocol_factory()
1724+
except:
1725+
# gh-156400: no transport owns the pipe yet, so close it.
1726+
pipe.close()
1727+
raise
17181728
waiter = self.create_future()
17191729
transport = self._make_read_pipe_transport(pipe, protocol, waiter)
17201730

@@ -1730,7 +1740,12 @@ 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()
1743+
try:
1744+
protocol = protocol_factory()
1745+
except:
1746+
# gh-156400: no transport owns the pipe yet, so close it.
1747+
pipe.close()
1748+
raise
17341749
waiter = self.create_future()
17351750
transport = self._make_write_pipe_transport(pipe, protocol, waiter)
17361751

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()`` raises
2+
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)