Skip to content

Commit 5153c7e

Browse files
committed
bpo-39850: Add support for abstract sockets in multiprocessing
1 parent 0911ea5 commit 5153c7e

File tree

6 files changed

+56
-5
lines changed

6 files changed

+56
-5
lines changed

Lib/multiprocessing/connection.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ def arbitrary_address(family):
7373
if family == 'AF_INET':
7474
return ('localhost', 0)
7575
elif family == 'AF_UNIX':
76+
# Prefer abstract sockets if possible to avoid problems with the address
77+
# size. When coding portable applications, some implementations have
78+
# sun_path as short as 92 bytes in the sockaddr_un struct.
79+
if util.abstract_sockets_supported:
80+
return f"\0listener-{os.getpid()}-{next(_mmap_counter)}"
7681
return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
7782
elif family == 'AF_PIPE':
7883
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
@@ -102,7 +107,7 @@ def address_type(address):
102107
return 'AF_INET'
103108
elif type(address) is str and address.startswith('\\\\'):
104109
return 'AF_PIPE'
105-
elif type(address) is str:
110+
elif type(address) is str or util.is_abstract_socket_namespace(address):
106111
return 'AF_UNIX'
107112
else:
108113
raise ValueError('address type of %r unrecognized' % address)
@@ -597,7 +602,8 @@ def __init__(self, address, family, backlog=1):
597602
self._family = family
598603
self._last_accepted = None
599604

600-
if family == 'AF_UNIX':
605+
if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
606+
# Linux abstract socket namespaces do not need to be unlinked
601607
self._unlink = util.Finalize(
602608
self, os.unlink, args=(address,), exitpriority=0
603609
)

Lib/multiprocessing/forkserver.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ def _stop_unlocked(self):
5555
os.waitpid(self._forkserver_pid, 0)
5656
self._forkserver_pid = None
5757

58-
os.unlink(self._forkserver_address)
58+
if not util.is_abstract_socket_namespace(self._forkserver_address):
59+
os.unlink(self._forkserver_address)
5960
self._forkserver_address = None
6061

6162
def set_forkserver_preload(self, modules_names):
@@ -135,7 +136,8 @@ def ensure_running(self):
135136
with socket.socket(socket.AF_UNIX) as listener:
136137
address = connection.arbitrary_address('AF_UNIX')
137138
listener.bind(address)
138-
os.chmod(address, 0o600)
139+
if not util.is_abstract_socket_namespace(address):
140+
os.chmod(address, 0o600)
139141
listener.listen()
140142

141143
# all client processes own the write end of the "alive" pipe;

Lib/multiprocessing/managers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,8 +1262,12 @@ class SharedMemoryServer(Server):
12621262

12631263
def __init__(self, *args, **kwargs):
12641264
Server.__init__(self, *args, **kwargs)
1265+
address = self.address
1266+
# The address of Linux abstract namespaces can be bytes
1267+
if isinstance(address, bytes):
1268+
address = address.decode()
12651269
self.shared_memory_context = \
1266-
_SharedMemoryTracker(f"shmm_{self.address}_{getpid()}")
1270+
_SharedMemoryTracker(f"shmm_{address}_{getpid()}")
12671271
util.debug(f"SharedMemoryServer started by pid {getpid()}")
12681272

12691273
def create(self, c, typeid, /, *args, **kwargs):

Lib/multiprocessing/util.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@ def log_to_stderr(level=None):
102102
_log_to_stderr = True
103103
return _logger
104104

105+
106+
# Abstract socket support
107+
108+
def _platform_supports_abstract_sockets():
109+
if "linux" in sys.platform:
110+
return True
111+
if hasattr(sys, 'getandroidapilevel'):
112+
return True
113+
return False
114+
115+
116+
def is_abstract_socket_namespace(address):
117+
if isinstance(address, bytes):
118+
return address[0] == 0
119+
elif isinstance(address, str):
120+
return address[0] == "\0"
121+
raise ValueError('address type of %r unrecognized' % address)
122+
123+
124+
abstract_sockets_supported = _platform_supports_abstract_sockets()
125+
105126
#
106127
# Function returning a temp directory which will be removed on exit
107128
#

Lib/test/_test_multiprocessing.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3274,6 +3274,19 @@ def test_context(self):
32743274
if self.TYPE == 'processes':
32753275
self.assertRaises(OSError, l.accept)
32763276

3277+
@unittest.skipUnless(util.abstract_sockets_supported,
3278+
"test needs abstract socket support")
3279+
def test_abstract_socket(self):
3280+
with self.connection.Listener("\0something") as l:
3281+
with self.connection.Client(l.address) as c:
3282+
with l.accept() as d:
3283+
c.send(1729)
3284+
self.assertEqual(d.recv(), 1729)
3285+
3286+
if self.TYPE == 'processes':
3287+
self.assertRaises(OSError, l.accept)
3288+
3289+
32773290
class _TestListenerClient(BaseTestCase):
32783291

32793292
ALLOWED_TYPES = ('processes', 'threads')
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:mod:`multiprocessing` now supports abstract socket addresses (if abstract sockets
2+
are supported in the running platform). When creating arbitrary addresses (like when
3+
default-constructing :class:`multiprocessing.connection.Listener` objects) abstract
4+
sockets are preferred to avoid the case when the temporary-file-generated address is
5+
too large for an AF_UNIX socket address). Patch by Pablo Galindo.

0 commit comments

Comments
 (0)