From 30d01a1e13d03c66e885bb8b0aba0ac3feacfc6c Mon Sep 17 00:00:00 2001 From: Harel Talasazan Date: Sun, 23 Aug 2026 15:00:34 -0700 Subject: [PATCH 1/2] gh-155912: Catch OSError from os.sysconf in _check_system_limits --- Lib/concurrent/futures/process.py | 5 ++- .../test_process_pool.py | 41 +++++++++++++++++++ ...-08-23-14-54-39.gh-issue-155912.Hk7Qa2.rst | 3 ++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-23-14-54-39.gh-issue-155912.Hk7Qa2.rst diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index c130259acb737ab..5ff660b89b2615d 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -707,8 +707,9 @@ def _check_system_limits(): raise NotImplementedError(_system_limited) try: nsems_max = os.sysconf("SC_SEM_NSEMS_MAX") - except (AttributeError, ValueError): - # sysconf not available or setting not available + except (AttributeError, ValueError, OSError): + # sysconf not available, setting not available, or the read was + # denied (e.g. a sandbox profile that denies sysctl reads on macOS) return if nsems_max == -1: # indetermined limit, assume that limit is determined diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index dafbda862c51c24..61a08e9a42c384f 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -8,6 +8,7 @@ import unittest.mock import weakref from concurrent import futures +from concurrent.futures import process as futures_process from concurrent.futures.process import BrokenProcessPool from test import support @@ -543,6 +544,46 @@ def test_force_shutdown_workers_stops_pool(self, function_name): break +class CheckSystemLimitsTest(unittest.TestCase): + # gh-155912: a denied sysconf read is the same condition as an + # unavailable one and must not be fatal. + + def setUp(self): + saved = (futures_process._system_limits_checked, + futures_process._system_limited) + + def restore(): + (futures_process._system_limits_checked, + futures_process._system_limited) = saved + + self.addCleanup(restore) + + def check(self, **kwargs): + futures_process._system_limits_checked = False + futures_process._system_limited = None + with unittest.mock.patch.object(os, "sysconf", **kwargs): + futures_process._check_system_limits() + + def test_denied_read_is_not_fatal(self): + self.check(side_effect=PermissionError(1, "Operation not permitted")) + + def test_other_oserror_is_not_fatal(self): + self.check(side_effect=OSError(5, "Input/output error")) + + def test_unsupported_name_is_not_fatal(self): + self.check(side_effect=ValueError) + + def test_indeterminate_limit_is_not_fatal(self): + self.check(return_value=-1) + + def test_sufficient_semaphores(self): + self.check(return_value=87381) + + def test_too_few_semaphores_still_raises(self): + with self.assertRaises(NotImplementedError): + self.check(return_value=10) + + create_executor_tests(globals(), ProcessPoolExecutorTest, executor_mixins=(ProcessPoolForkMixin, ProcessPoolForkserverMixin, diff --git a/Misc/NEWS.d/next/Library/2026-08-23-14-54-39.gh-issue-155912.Hk7Qa2.rst b/Misc/NEWS.d/next/Library/2026-08-23-14-54-39.gh-issue-155912.Hk7Qa2.rst new file mode 100644 index 000000000000000..b51f0590beecf19 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-23-14-54-39.gh-issue-155912.Hk7Qa2.rst @@ -0,0 +1,3 @@ +Fix :class:`concurrent.futures.ProcessPoolExecutor` construction when +:func:`os.sysconf` raises :exc:`OSError`, for instance when a sandbox denies +the read. From 5f63358fb9049b6ee209f650c55f1d8e1d95b847 Mon Sep 17 00:00:00 2001 From: Harel Talasazan Date: Mon, 24 Aug 2026 09:00:13 -0700 Subject: [PATCH 2/2] gh-155912: Skip CheckSystemLimitsTest where it cannot run --- Lib/test/test_concurrent_futures/test_process_pool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 61a08e9a42c384f..052064d97f19e08 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -544,11 +544,13 @@ def test_force_shutdown_workers_stops_pool(self, function_name): break +@unittest.skipUnless(hasattr(os, "sysconf"), "requires os.sysconf()") class CheckSystemLimitsTest(unittest.TestCase): # gh-155912: a denied sysconf read is the same condition as an # unavailable one and must not be fatal. def setUp(self): + support.skip_if_broken_multiprocessing_synchronize() saved = (futures_process._system_limits_checked, futures_process._system_limited)