Skip to content

Commit 8b58d83

Browse files
Merge pull request #84762 from charles-zablit/charles-zablit/update-checkout/fix-socket-error
[update-checkout] fallback to verbose mode if tempdir is too long
2 parents f2615c1 + ebd6b99 commit 8b58d83

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

utils/update_checkout/tests/test_clone.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import os
1414

1515
from . import scheme_mock
16+
from unittest import mock
1617

1718

1819
class CloneTestCase(scheme_mock.SchemeMockTestCase):
@@ -42,6 +43,20 @@ def test_clone_with_additional_scheme(self):
4243
# Test that we're actually checking out the 'extra' scheme based on the output
4344
self.assertIn(b"git checkout refs/heads/main", output)
4445

46+
def test_manager_not_called_on_long_socket(self):
47+
fake_tmpdir = '/tmp/very/' + '/long' * 20 + '/tmp'
48+
49+
with mock.patch('tempfile.gettempdir', return_value=fake_tmpdir), \
50+
mock.patch('multiprocessing.Manager') as mock_manager:
51+
52+
self.call([self.update_checkout_path,
53+
'--config', self.config_path,
54+
'--source-root', self.source_root,
55+
'--clone'])
56+
# Ensure that we do not try to create a Manager when the tempdir
57+
# is too long.
58+
mock_manager.assert_not_called()
59+
4560

4661
class SchemeWithMissingRepoTestCase(scheme_mock.SchemeMockTestCase):
4762
def __init__(self, *args, **kwargs):

utils/update_checkout/update_checkout/parallel_runner.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,30 @@ def __init__(
4848
):
4949
self._monitor_polling_period = 0.1
5050
if n_processes == 0:
51-
n_processes = cpu_count() * 2
51+
if sys.version_info.minor < 10:
52+
# On Python < 3.10, https://bugs.python.org/issue46391 causes
53+
# Pool.map and its variants to hang. Limiting the number of
54+
# processes fixes the issue.
55+
n_processes = int(cpu_count() * 1.25)
56+
else:
57+
n_processes = cpu_count() * 2
5258
self._terminal_width = shutil.get_terminal_size().columns
5359
self._n_processes = n_processes
5460
self._pool_args = pool_args
55-
manager = Manager()
56-
self._lock = manager.Lock()
57-
self._running_tasks = manager.list()
58-
self._updated_repos = manager.Value("i", 0)
5961
self._fn = fn
6062
self._pool = Pool(processes=self._n_processes)
61-
self._verbose = pool_args[0].verbose
6263
self._output_prefix = pool_args[0].output_prefix
6364
self._nb_repos = len(pool_args)
6465
self._stop_event = Event()
65-
self._monitored_fn = MonitoredFunction(
66-
self._fn, self._running_tasks, self._updated_repos, self._lock
67-
)
66+
self._verbose = pool_args[0].verbose
67+
if not self._verbose:
68+
manager = Manager()
69+
self._lock = manager.Lock()
70+
self._running_tasks = manager.list()
71+
self._updated_repos = manager.Value("i", 0)
72+
self._monitored_fn = MonitoredFunction(
73+
self._fn, self._running_tasks, self._updated_repos, self._lock
74+
)
6875

6976
def run(self) -> List[Any]:
7077
print(

utils/update_checkout/update_checkout/update_checkout.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import os
1414
import platform
1515
import re
16+
import tempfile
1617
import sys
1718
import traceback
1819
from multiprocessing import freeze_support
@@ -27,6 +28,29 @@
2728
SCRIPT_FILE = os.path.abspath(__file__)
2829
SCRIPT_DIR = os.path.dirname(SCRIPT_FILE)
2930

31+
def is_unix_sock_path_too_long() -> bool:
32+
"""Check if the unix socket_path exceeds the 104 limit (108 on Linux).
33+
34+
The multiprocessing module creates a socket in the TEMPDIR folder. The
35+
socket path should not exceed:
36+
- 104 bytes on macOS
37+
- 108 bytes on Linux (https://www.man7.org/linux/man-pages/man7/unix.7.html)
38+
39+
Returns:
40+
bool: Whether the socket path exceeds the limit. Always False on Windows.
41+
"""
42+
43+
if os.name != "posix":
44+
return False
45+
46+
MAX_UNIX_SOCKET_PATH = 104
47+
# `tempfile.mktemp` is deprecated yet that's what the multiprocessing
48+
# module uses internally: https://github.com/python/cpython/blob/c4e7d245d61ac4547ecf3362b28f64fc00aa88c0/Lib/multiprocessing/connection.py#L72
49+
# Since we are not using the resulting file, it is safe to use this
50+
# method.
51+
sock_path = tempfile.mktemp(prefix="sock-", dir=tempfile.gettempdir())
52+
return len(sock_path.encode("utf-8")) > MAX_UNIX_SOCKET_PATH
53+
3054
def confirm_tag_in_repo(tag: str, repo_name: str) -> Optional[str]:
3155
"""Confirm that a given tag exists in a git repository. This function
3256
assumes that the repository is already a current working directory before
@@ -771,6 +795,13 @@ def main():
771795
"specify --scheme=foo")
772796
sys.exit(1)
773797

798+
if is_unix_sock_path_too_long():
799+
print(
800+
f"TEMPDIR={tempfile.gettempdir()} is too long and multiprocessing "
801+
"sockets will exceed the size limit. Falling back to verbose mode."
802+
)
803+
args.verbose = True
804+
774805
clone = args.clone
775806
clone_with_ssh = args.clone_with_ssh
776807
skip_history = args.skip_history

0 commit comments

Comments
 (0)