Skip to content

fix(docker): handle cgroup v2 "max" memory limit in container memory guard - #2154

Open
Battleplus wants to merge 2 commits into
unclecode:mainfrom
Battleplus:fix/docker-cgroup-v2-max-memory-guard
Open

fix(docker): handle cgroup v2 "max" memory limit in container memory guard#2154
Battleplus wants to merge 2 commits into
unclecode:mainfrom
Battleplus:fix/docker-cgroup-v2-max-memory-guard

Conversation

@Battleplus

@Battleplus Battleplus commented Aug 19, 2026

Copy link
Copy Markdown

Fixes #2123

Problem

get_container_memory_percent() in deploy/docker/utils.py reads the cgroup memory limit with int(limit_path.read_text()). On cgroup v2 with no container memory limit set (no -m / mem_limit), /sys/fs/cgroup/memory.max contains the literal string max, so int("max") raises ValueError. The bare except swallows it and the function returns psutil.virtual_memory().percent — the host's usage percentage.

The existing if limit > 1e18 "unlimited" branch (whose comment even names the v2 "max" form) was unreachable on v2 because the exception fired two lines earlier.

Effects:

  • memory_threshold_percent no longer guards the container; on a 16 GB host the guard effectively watched host-wide usage (~15.2 GB at the 95% default) while the container itself could OOM.
  • The reading is coupled to unrelated containers (a memory-hungry neighbour can make crawl4ai refuse crawls while idle).
  • The failure is silent (bare except, no log), so a defeated guard looks like a working one.

Fix

Normalize the cgroup v2 max sentinel to the same "unlimited" value the v1 > 1e18 branch already handled, so the guard reports container usage against host total when no limit is set — the behavior the docstring and the existing branch already intended. Also .strip()s the raw file contents.

Tests

Adds deploy/docker/tests/test_utils_memory.py — dependency-free unit tests (stub the module-level third-party imports so they run without the Docker dev env) covering:

  • cgroup v2 numeric limit → usage/limit
  • cgroup v2 max (unlimited) → usage/host-total (regression for this issue)
  • cgroup v1 numeric limit → usage/limit
  • cgroup v1 > 1e18 unlimited sentinel → usage/host-total (existing intent preserved)
  • non-container (no cgroup files) → host percent fallback
5 passed in 0.03s

Files changed

  • deploy/docker/utils.py — normalize the cgroup v2 max sentinel to the same "unlimited" value the v1 > 1e18 branch already handled.
  • deploy/docker/tests/test_utils_memory.py — new dependency-free unit tests for the memory guard.
  • .github/workflows/security.yml — run the docker utility unit tests in the security workflow. The tests use a test_utils_*.py prefix, which the existing test_security_*.py glob did not match, so they would never have run in CI without this.

Battleplus and others added 2 commits August 19, 2026 16:40
…guard

Closes unclecode#2123

cgroup v2 reports an unset memory limit as the literal string "max".
int("max") raised a ValueError that the bare except swallowed, so the
guard silently fell back to the HOST's usage percent instead of the
container's usage vs host total (the intent of the existing v1 > 1e18
"unlimited" branch, which was unreachable on v2).

Normalize "max" to the same unlimited sentinel so memory_threshold_percent
guards the container even when no -m / mem_limit is set. Adds dependency-free
unit tests covering v2 "max", v2/v1 numeric limits, the v1 unlimited sentinel
and the non-container host-percent fallback.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
test_utils_memory.py (added for unclecode#2123) was not matched by the existing
'test_security_*.py' glob, so the regression coverage would never execute
in CI. Add a 'test_utils_*.py' run to the offline security job — the
stub-import tests are pure-function, need no Docker/network, and run in
milliseconds.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@Battleplus

Copy link
Copy Markdown
Author

Follow-up commit: the new test file deploy/docker/tests/test_utils_memory.py was not matched by the security workflow's existing test_security_*.py glob, so the regression coverage would never have executed in CI. Added a test_utils_*.py run to the offline security job (pure-function tests, no Docker/network needed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Docker memory guard reads host RAM when no container limit is set — cgroup v2 "max" defeats get_container_memory_percent

1 participant