Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 74b868f

Browse files
authored
gh-111246: Remove listening Unix socket on close (#111483)
Try to clean up the socket file we create so we don't add unused noise to the file system.
1 parent f88caab commit 74b868f

File tree

5 files changed

+126
-2
lines changed

5 files changed

+126
-2
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ Creating network servers
778778
*, sock=None, backlog=100, ssl=None, \
779779
ssl_handshake_timeout=None, \
780780
ssl_shutdown_timeout=None, \
781-
start_serving=True)
781+
start_serving=True, cleanup_socket=True)
782782
783783
Similar to :meth:`loop.create_server` but works with the
784784
:py:const:`~socket.AF_UNIX` socket family.
@@ -788,6 +788,10 @@ Creating network servers
788788
:class:`str`, :class:`bytes`, and :class:`~pathlib.Path` paths
789789
are supported.
790790

791+
If *cleanup_socket* is True then the Unix socket will automatically
792+
be removed from the filesystem when the server is closed, unless the
793+
socket has been replaced after the server has been created.
794+
791795
See the documentation of the :meth:`loop.create_server` method
792796
for information about arguments to this method.
793797

@@ -802,6 +806,10 @@ Creating network servers
802806

803807
Added the *ssl_shutdown_timeout* parameter.
804808

809+
.. versionchanged:: 3.13
810+
811+
Added the *cleanup_socket* parameter.
812+
805813

806814
.. coroutinemethod:: loop.connect_accepted_socket(protocol_factory, \
807815
sock, *, ssl=None, ssl_handshake_timeout=None, \

Doc/whatsnew/3.13.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ array
149149
It can be used instead of ``'u'`` type code, which is deprecated.
150150
(Contributed by Inada Naoki in :gh:`80480`.)
151151

152+
asyncio
153+
-------
154+
155+
* :meth:`asyncio.loop.create_unix_server` will now automatically remove
156+
the Unix socket when the server is closed.
157+
(Contributed by Pierre Ossman in :gh:`111246`.)
158+
152159
copy
153160
----
154161

Lib/asyncio/unix_events.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
6464
def __init__(self, selector=None):
6565
super().__init__(selector)
6666
self._signal_handlers = {}
67+
self._unix_server_sockets = {}
6768

6869
def close(self):
6970
super().close()
@@ -284,7 +285,7 @@ async def create_unix_server(
284285
sock=None, backlog=100, ssl=None,
285286
ssl_handshake_timeout=None,
286287
ssl_shutdown_timeout=None,
287-
start_serving=True):
288+
start_serving=True, cleanup_socket=True):
288289
if isinstance(ssl, bool):
289290
raise TypeError('ssl argument must be an SSLContext or None')
290291

@@ -340,6 +341,15 @@ async def create_unix_server(
340341
raise ValueError(
341342
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
342343

344+
if cleanup_socket:
345+
path = sock.getsockname()
346+
# Check for abstract socket. `str` and `bytes` paths are supported.
347+
if path[0] not in (0, '\x00'):
348+
try:
349+
self._unix_server_sockets[sock] = os.stat(path).st_ino
350+
except FileNotFoundError:
351+
pass
352+
343353
sock.setblocking(False)
344354
server = base_events.Server(self, [sock], protocol_factory,
345355
ssl, backlog, ssl_handshake_timeout,
@@ -460,6 +470,27 @@ def cb(fut):
460470
self.remove_writer(fd)
461471
fut.add_done_callback(cb)
462472

473+
def _stop_serving(self, sock):
474+
# Is this a unix socket that needs cleanup?
475+
if sock in self._unix_server_sockets:
476+
path = sock.getsockname()
477+
else:
478+
path = None
479+
480+
super()._stop_serving(sock)
481+
482+
if path is not None:
483+
prev_ino = self._unix_server_sockets[sock]
484+
del self._unix_server_sockets[sock]
485+
try:
486+
if os.stat(path).st_ino == prev_ino:
487+
os.unlink(path)
488+
except FileNotFoundError:
489+
pass
490+
except OSError as err:
491+
logger.error('Unable to clean up listening UNIX socket '
492+
'%r: %r', path, err)
493+
463494

464495
class _UnixReadPipeTransport(transports.ReadTransport):
465496

Lib/test/test_asyncio/test_server.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import asyncio
2+
import os
3+
import socket
24
import time
35
import threading
46
import unittest
@@ -177,6 +179,80 @@ async def serve(*args):
177179

178180

179181

182+
# Test the various corner cases of Unix server socket removal
183+
class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):
184+
@socket_helper.skip_unless_bind_unix_socket
185+
async def test_unix_server_addr_cleanup(self):
186+
# Default scenario
187+
with test_utils.unix_socket_path() as addr:
188+
async def serve(*args):
189+
pass
190+
191+
srv = await asyncio.start_unix_server(serve, addr)
192+
193+
srv.close()
194+
self.assertFalse(os.path.exists(addr))
195+
196+
@socket_helper.skip_unless_bind_unix_socket
197+
async def test_unix_server_sock_cleanup(self):
198+
# Using already bound socket
199+
with test_utils.unix_socket_path() as addr:
200+
async def serve(*args):
201+
pass
202+
203+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
204+
sock.bind(addr)
205+
206+
srv = await asyncio.start_unix_server(serve, sock=sock)
207+
208+
srv.close()
209+
self.assertFalse(os.path.exists(addr))
210+
211+
@socket_helper.skip_unless_bind_unix_socket
212+
async def test_unix_server_cleanup_gone(self):
213+
# Someone else has already cleaned up the socket
214+
with test_utils.unix_socket_path() as addr:
215+
async def serve(*args):
216+
pass
217+
218+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
219+
sock.bind(addr)
220+
221+
srv = await asyncio.start_unix_server(serve, sock=sock)
222+
223+
os.unlink(addr)
224+
225+
srv.close()
226+
227+
@socket_helper.skip_unless_bind_unix_socket
228+
async def test_unix_server_cleanup_replaced(self):
229+
# Someone else has replaced the socket with their own
230+
with test_utils.unix_socket_path() as addr:
231+
async def serve(*args):
232+
pass
233+
234+
srv = await asyncio.start_unix_server(serve, addr)
235+
236+
os.unlink(addr)
237+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
238+
sock.bind(addr)
239+
240+
srv.close()
241+
self.assertTrue(os.path.exists(addr))
242+
243+
@socket_helper.skip_unless_bind_unix_socket
244+
async def test_unix_server_cleanup_prevented(self):
245+
# Automatic cleanup explicitly disabled
246+
with test_utils.unix_socket_path() as addr:
247+
async def serve(*args):
248+
pass
249+
250+
srv = await asyncio.start_unix_server(serve, addr, cleanup_socket=False)
251+
252+
srv.close()
253+
self.assertTrue(os.path.exists(addr))
254+
255+
180256
@unittest.skipUnless(hasattr(asyncio, 'ProactorEventLoop'), 'Windows only')
181257
class ProactorStartServerTests(BaseStartServer, unittest.TestCase):
182258

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:meth:`asyncio.loop.create_unix_server` will now automatically remove the
2+
Unix socket when the server is closed.

0 commit comments

Comments
 (0)