Skip to content

Commit 94599dc

Browse files
authored
Respect cgroup CPU limits when running inside containers (#3749)
1 parent f6b4803 commit 94599dc

File tree

1 file changed

+52
-1
lines changed

1 file changed

+52
-1
lines changed

src/ansiblelint/runner.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import json
55
import logging
6+
import math
67
import multiprocessing
78
import multiprocessing.pool
89
import os
@@ -12,6 +13,7 @@
1213
import warnings
1314
from dataclasses import dataclass
1415
from fnmatch import fnmatch
16+
from functools import cache
1517
from pathlib import Path
1618
from typing import TYPE_CHECKING, Any
1719

@@ -250,7 +252,7 @@ def worker(lintable: Lintable) -> list[MatchError]:
250252
# pylint: disable=unused-variable
251253
global_resource = multiprocessing.Semaphore() # noqa: F841
252254

253-
pool = multiprocessing.pool.ThreadPool(processes=multiprocessing.cpu_count())
255+
pool = multiprocessing.pool.ThreadPool(processes=threads())
254256
return_list = pool.map(worker, files, chunksize=1)
255257
pool.close()
256258
pool.join()
@@ -534,6 +536,55 @@ def play_children(
534536
return []
535537

536538

539+
@cache
540+
def threads() -> int:
541+
"""Determine how many threads to use.
542+
543+
Inside containers we want to respect limits imposed.
544+
545+
When present /sys/fs/cgroup/cpu.max can contain something like:
546+
$ podman/docker run -it --rm --cpus 1.5 ubuntu:latest cat /sys/fs/cgroup/cpu.max
547+
150000 100000
548+
# "max 100000" is returned when no limits are set.
549+
550+
See: https://github.com/python/cpython/issues/80235
551+
See: https://github.com/python/cpython/issues/70879
552+
"""
553+
os_cpu_count = multiprocessing.cpu_count()
554+
# Cgroup CPU bandwidth limit available in Linux since 2.6 kernel
555+
556+
cpu_max_fname = "/sys/fs/cgroup/cpu.max"
557+
cfs_quota_fname = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
558+
cfs_period_fname = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
559+
if os.path.exists(cpu_max_fname):
560+
# cgroup v2
561+
# https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
562+
with open(cpu_max_fname, encoding="utf-8") as fh:
563+
cpu_quota_us, cpu_period_us = fh.read().strip().split()
564+
elif os.path.exists(cfs_quota_fname) and os.path.exists(cfs_period_fname):
565+
# cgroup v1
566+
# https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html#management
567+
with open(cfs_quota_fname, encoding="utf-8") as fh:
568+
cpu_quota_us = fh.read().strip()
569+
with open(cfs_period_fname, encoding="utf-8") as fh:
570+
cpu_period_us = fh.read().strip()
571+
else:
572+
# No Cgroup CPU bandwidth limit (e.g. non-Linux platform)
573+
cpu_quota_us = "max"
574+
cpu_period_us = "100000" # unused, for consistency with default values
575+
576+
if cpu_quota_us == "max":
577+
# No active Cgroup quota on a Cgroup-capable platform
578+
return os_cpu_count
579+
cpu_quota_us_int = int(cpu_quota_us)
580+
cpu_period_us_int = int(cpu_period_us)
581+
if cpu_quota_us_int > 0 and cpu_period_us_int > 0:
582+
return math.ceil(cpu_quota_us_int / cpu_period_us_int)
583+
# Setting a negative cpu_quota_us value is a valid way to disable
584+
# cgroup CPU bandwidth limits
585+
return os_cpu_count
586+
587+
537588
def _get_matches(rules: RulesCollection, options: Options) -> LintResult:
538589
lintables = ansiblelint.utils.get_lintables(opts=options, args=options.lintables)
539590

0 commit comments

Comments
 (0)