Skip to content

Commit 1971224

Browse files
authored
Merge pull request #1180 from projectsyn/fix/respect-sched-affinity
Fix overcommitted thread counts on Linux
2 parents cef88a1 + ebc8ebc commit 1971224

File tree

3 files changed

+57
-2
lines changed

3 files changed

+57
-2
lines changed

commodore/cli/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from commodore import __git_version__, __version__, tools
1414
from commodore.config import Config
15+
from commodore.helpers import cpu_count
1516
from commodore.version import version_info
1617

1718
import commodore.cli.options as options
@@ -85,7 +86,10 @@ def commodore(ctx, working_dir, verbose, request_timeout):
8586

8687
def main():
8788
multiprocessing.set_start_method("spawn")
88-
Reclass.set_thread_count(0)
89+
# `Reclass.set_thread_count()` wants 0 if it should use its internal detection logic, so we call
90+
# the `cpu_count()` helper with `fallback=0` to return 0 if the number of CPUs can't be
91+
# determined.
92+
Reclass.set_thread_count(cpu_count(fallback=0))
8993
tools.setup_path()
9094

9195
load_dotenv(dotenv_path=find_dotenv(usecwd=True))

commodore/helpers.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def kapitan_compile(
271271
cached.args.verbose = config.trace
272272
cached.args.output_path = output_dir
273273
cached.args.targets = targets
274-
cached.args.parallelism = None
274+
cached.args.parallelism = cpu_count()
275275
cached.args.labels = None
276276
cached.args.prune = False
277277
cached.args.indent = 2
@@ -385,3 +385,15 @@ def python3_executable() -> str:
385385
fg="yellow",
386386
)
387387
return path_python
388+
389+
390+
def cpu_count(fallback: Optional[int] = None) -> Optional[int]:
391+
"""Return the number of available CPU cores or `fallback` if CPU count can't be determined.
392+
393+
On Linux this respects the process's scheduling affinity.
394+
395+
NOTE(sg): This can be replaced by `os.process_cpu_count() on Python >= 3.13.
396+
"""
397+
if hasattr(os, "sched_getaffinity"):
398+
return len(os.sched_getaffinity(0))
399+
return os.cpu_count() or fallback

tests/test_helpers.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,42 @@ def test_python3_executable_err(mock_shutil, mock_sys, capsys):
503503
"[ERROR] No python3 in $PATH, components which use `${_python3}` will fail."
504504
in out
505505
)
506+
507+
508+
class MockOS:
509+
_cpu_count: Optional[int]
510+
_sched_affinity: list[int]
511+
512+
def __init__(self):
513+
self._cpu_count = None
514+
self._sched_affinitiy = []
515+
516+
def cpu_count(self) -> Optional[int]:
517+
return self._cpu_count
518+
519+
520+
class MockOSAff(MockOS):
521+
def sched_getaffinity(self, pid: int) -> list[int]:
522+
assert pid == 0
523+
return self._sched_affinity
524+
525+
526+
@pytest.mark.parametrize(
527+
"cpu_count,fallback", [(None, None), (None, 0), (4, None), (4, 0)]
528+
)
529+
@patch.object(helpers, "os", new_callable=MockOS)
530+
def test_cpu_count(mock_os, cpu_count, fallback):
531+
mock_os._cpu_count = cpu_count
532+
r = helpers.cpu_count(fallback=fallback)
533+
if cpu_count is None:
534+
assert r == fallback
535+
else:
536+
assert r == cpu_count
537+
538+
539+
@pytest.mark.parametrize("sched_affinity", [[0, 3], [0, 1, 2, 3, 4, 5, 6]])
540+
@patch.object(helpers, "os", new_callable=MockOSAff)
541+
def test_cpu_count_affinity(mock_os, sched_affinity):
542+
mock_os._cpu_count = 4
543+
mock_os._sched_affinity = sched_affinity
544+
assert helpers.cpu_count() == len(sched_affinity)

0 commit comments

Comments
 (0)