mirror of
https://github.com/python/cpython.git
synced 2025-09-18 22:50:26 +00:00
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.
This commit is contained in:
parent
f88caab467
commit
74b868f636
5 changed files with 126 additions and 2 deletions
|
@ -778,7 +778,7 @@ Creating network servers
|
||||||
*, sock=None, backlog=100, ssl=None, \
|
*, sock=None, backlog=100, ssl=None, \
|
||||||
ssl_handshake_timeout=None, \
|
ssl_handshake_timeout=None, \
|
||||||
ssl_shutdown_timeout=None, \
|
ssl_shutdown_timeout=None, \
|
||||||
start_serving=True)
|
start_serving=True, cleanup_socket=True)
|
||||||
|
|
||||||
Similar to :meth:`loop.create_server` but works with the
|
Similar to :meth:`loop.create_server` but works with the
|
||||||
:py:const:`~socket.AF_UNIX` socket family.
|
:py:const:`~socket.AF_UNIX` socket family.
|
||||||
|
@ -788,6 +788,10 @@ Creating network servers
|
||||||
:class:`str`, :class:`bytes`, and :class:`~pathlib.Path` paths
|
:class:`str`, :class:`bytes`, and :class:`~pathlib.Path` paths
|
||||||
are supported.
|
are supported.
|
||||||
|
|
||||||
|
If *cleanup_socket* is True then the Unix socket will automatically
|
||||||
|
be removed from the filesystem when the server is closed, unless the
|
||||||
|
socket has been replaced after the server has been created.
|
||||||
|
|
||||||
See the documentation of the :meth:`loop.create_server` method
|
See the documentation of the :meth:`loop.create_server` method
|
||||||
for information about arguments to this method.
|
for information about arguments to this method.
|
||||||
|
|
||||||
|
@ -802,6 +806,10 @@ Creating network servers
|
||||||
|
|
||||||
Added the *ssl_shutdown_timeout* parameter.
|
Added the *ssl_shutdown_timeout* parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
|
||||||
|
Added the *cleanup_socket* parameter.
|
||||||
|
|
||||||
|
|
||||||
.. coroutinemethod:: loop.connect_accepted_socket(protocol_factory, \
|
.. coroutinemethod:: loop.connect_accepted_socket(protocol_factory, \
|
||||||
sock, *, ssl=None, ssl_handshake_timeout=None, \
|
sock, *, ssl=None, ssl_handshake_timeout=None, \
|
||||||
|
|
|
@ -149,6 +149,13 @@ array
|
||||||
It can be used instead of ``'u'`` type code, which is deprecated.
|
It can be used instead of ``'u'`` type code, which is deprecated.
|
||||||
(Contributed by Inada Naoki in :gh:`80480`.)
|
(Contributed by Inada Naoki in :gh:`80480`.)
|
||||||
|
|
||||||
|
asyncio
|
||||||
|
-------
|
||||||
|
|
||||||
|
* :meth:`asyncio.loop.create_unix_server` will now automatically remove
|
||||||
|
the Unix socket when the server is closed.
|
||||||
|
(Contributed by Pierre Ossman in :gh:`111246`.)
|
||||||
|
|
||||||
copy
|
copy
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
def __init__(self, selector=None):
|
def __init__(self, selector=None):
|
||||||
super().__init__(selector)
|
super().__init__(selector)
|
||||||
self._signal_handlers = {}
|
self._signal_handlers = {}
|
||||||
|
self._unix_server_sockets = {}
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
super().close()
|
super().close()
|
||||||
|
@ -284,7 +285,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
sock=None, backlog=100, ssl=None,
|
sock=None, backlog=100, ssl=None,
|
||||||
ssl_handshake_timeout=None,
|
ssl_handshake_timeout=None,
|
||||||
ssl_shutdown_timeout=None,
|
ssl_shutdown_timeout=None,
|
||||||
start_serving=True):
|
start_serving=True, cleanup_socket=True):
|
||||||
if isinstance(ssl, bool):
|
if isinstance(ssl, bool):
|
||||||
raise TypeError('ssl argument must be an SSLContext or None')
|
raise TypeError('ssl argument must be an SSLContext or None')
|
||||||
|
|
||||||
|
@ -340,6 +341,15 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
|
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
|
||||||
|
|
||||||
|
if cleanup_socket:
|
||||||
|
path = sock.getsockname()
|
||||||
|
# Check for abstract socket. `str` and `bytes` paths are supported.
|
||||||
|
if path[0] not in (0, '\x00'):
|
||||||
|
try:
|
||||||
|
self._unix_server_sockets[sock] = os.stat(path).st_ino
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
sock.setblocking(False)
|
sock.setblocking(False)
|
||||||
server = base_events.Server(self, [sock], protocol_factory,
|
server = base_events.Server(self, [sock], protocol_factory,
|
||||||
ssl, backlog, ssl_handshake_timeout,
|
ssl, backlog, ssl_handshake_timeout,
|
||||||
|
@ -460,6 +470,27 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
self.remove_writer(fd)
|
self.remove_writer(fd)
|
||||||
fut.add_done_callback(cb)
|
fut.add_done_callback(cb)
|
||||||
|
|
||||||
|
def _stop_serving(self, sock):
|
||||||
|
# Is this a unix socket that needs cleanup?
|
||||||
|
if sock in self._unix_server_sockets:
|
||||||
|
path = sock.getsockname()
|
||||||
|
else:
|
||||||
|
path = None
|
||||||
|
|
||||||
|
super()._stop_serving(sock)
|
||||||
|
|
||||||
|
if path is not None:
|
||||||
|
prev_ino = self._unix_server_sockets[sock]
|
||||||
|
del self._unix_server_sockets[sock]
|
||||||
|
try:
|
||||||
|
if os.stat(path).st_ino == prev_ino:
|
||||||
|
os.unlink(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except OSError as err:
|
||||||
|
logger.error('Unable to clean up listening UNIX socket '
|
||||||
|
'%r: %r', path, err)
|
||||||
|
|
||||||
|
|
||||||
class _UnixReadPipeTransport(transports.ReadTransport):
|
class _UnixReadPipeTransport(transports.ReadTransport):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -177,6 +179,80 @@ class TestServer2(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Test the various corner cases of Unix server socket removal
|
||||||
|
class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
@socket_helper.skip_unless_bind_unix_socket
|
||||||
|
async def test_unix_server_addr_cleanup(self):
|
||||||
|
# Default scenario
|
||||||
|
with test_utils.unix_socket_path() as addr:
|
||||||
|
async def serve(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
srv = await asyncio.start_unix_server(serve, addr)
|
||||||
|
|
||||||
|
srv.close()
|
||||||
|
self.assertFalse(os.path.exists(addr))
|
||||||
|
|
||||||
|
@socket_helper.skip_unless_bind_unix_socket
|
||||||
|
async def test_unix_server_sock_cleanup(self):
|
||||||
|
# Using already bound socket
|
||||||
|
with test_utils.unix_socket_path() as addr:
|
||||||
|
async def serve(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.bind(addr)
|
||||||
|
|
||||||
|
srv = await asyncio.start_unix_server(serve, sock=sock)
|
||||||
|
|
||||||
|
srv.close()
|
||||||
|
self.assertFalse(os.path.exists(addr))
|
||||||
|
|
||||||
|
@socket_helper.skip_unless_bind_unix_socket
|
||||||
|
async def test_unix_server_cleanup_gone(self):
|
||||||
|
# Someone else has already cleaned up the socket
|
||||||
|
with test_utils.unix_socket_path() as addr:
|
||||||
|
async def serve(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.bind(addr)
|
||||||
|
|
||||||
|
srv = await asyncio.start_unix_server(serve, sock=sock)
|
||||||
|
|
||||||
|
os.unlink(addr)
|
||||||
|
|
||||||
|
srv.close()
|
||||||
|
|
||||||
|
@socket_helper.skip_unless_bind_unix_socket
|
||||||
|
async def test_unix_server_cleanup_replaced(self):
|
||||||
|
# Someone else has replaced the socket with their own
|
||||||
|
with test_utils.unix_socket_path() as addr:
|
||||||
|
async def serve(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
srv = await asyncio.start_unix_server(serve, addr)
|
||||||
|
|
||||||
|
os.unlink(addr)
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.bind(addr)
|
||||||
|
|
||||||
|
srv.close()
|
||||||
|
self.assertTrue(os.path.exists(addr))
|
||||||
|
|
||||||
|
@socket_helper.skip_unless_bind_unix_socket
|
||||||
|
async def test_unix_server_cleanup_prevented(self):
|
||||||
|
# Automatic cleanup explicitly disabled
|
||||||
|
with test_utils.unix_socket_path() as addr:
|
||||||
|
async def serve(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
srv = await asyncio.start_unix_server(serve, addr, cleanup_socket=False)
|
||||||
|
|
||||||
|
srv.close()
|
||||||
|
self.assertTrue(os.path.exists(addr))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(asyncio, 'ProactorEventLoop'), 'Windows only')
|
@unittest.skipUnless(hasattr(asyncio, 'ProactorEventLoop'), 'Windows only')
|
||||||
class ProactorStartServerTests(BaseStartServer, unittest.TestCase):
|
class ProactorStartServerTests(BaseStartServer, unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:meth:`asyncio.loop.create_unix_server` will now automatically remove the
|
||||||
|
Unix socket when the server is closed.
|
Loading…
Add table
Add a link
Reference in a new issue