Skip to content

Commit e870ba2

Browse files
authored
Merge pull request #154 from FreeshardBase/fix/clayde/reclaim-eagain-log
fix(memory_pressure): don't warn on expected EAGAIN from memory.reclaim
2 parents 47ac78d + 4545132 commit e870ba2

2 files changed

Lines changed: 51 additions & 3 deletions

File tree

shard_core/service/memory_pressure.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import errno
23
import logging
34
import re
45
from pathlib import Path
@@ -50,9 +51,18 @@ def _reclaim_container(container_id: str):
5051
try:
5152
(cgroup / "memory.reclaim").write_text(f"{current}\n")
5253
except OSError as e:
53-
# Partial reclaim surfaces as EAGAIN once nothing more can be paged
54-
# out — the pages that could move are in swap already.
55-
log.warning(f"memory.reclaim incomplete for container {container_id}: {e}")
54+
# We request the container's full RSS, which can never be reclaimed in
55+
# whole (some pages are always resident), so memory.reclaim returns
56+
# EAGAIN on essentially every pause once it has paged out what it can —
57+
# that is the expected terminal signal, not a failure. Only surface a
58+
# warning for genuine errors (missing file, EPERM, ...).
59+
if e.errno == errno.EAGAIN:
60+
log.debug(
61+
f"memory.reclaim reached EAGAIN for container {container_id} "
62+
"(expected — paged out what it could)"
63+
)
64+
else:
65+
log.warning(f"memory.reclaim failed for container {container_id}: {e}")
5666

5767

5868
def _find_cgroup(container_id: str) -> Path | None:

tests/test_memory_pressure.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import errno
2+
import logging
3+
from pathlib import Path
14
from unittest.mock import AsyncMock, patch
25

36
import pytest
@@ -67,6 +70,41 @@ def test_reclaim_container_zero_usage_writes_nothing(fake_cgroup_root):
6770
assert (cgroup / "memory.reclaim").read_text() == ""
6871

6972

73+
def test_reclaim_container_eagain_is_debug_not_warning(
74+
fake_cgroup_root, caplog, monkeypatch
75+
):
76+
# Requesting the full RSS always ends in EAGAIN once nothing more can be
77+
# paged out — expected on every pause, so it must not log a warning.
78+
_make_cgroup(fake_cgroup_root, "docker/eag123", 4096)
79+
80+
def _raise_eagain(self, *args, **kwargs):
81+
raise OSError(errno.EAGAIN, "write could not complete without blocking")
82+
83+
monkeypatch.setattr(Path, "write_text", _raise_eagain)
84+
with caplog.at_level(logging.DEBUG, logger=memory_pressure.log.name):
85+
memory_pressure._reclaim_container("eag123")
86+
87+
assert not any(r.levelno == logging.WARNING for r in caplog.records)
88+
assert any("EAGAIN" in r.getMessage() for r in caplog.records)
89+
90+
91+
def test_reclaim_container_other_oserror_warns(fake_cgroup_root, caplog, monkeypatch):
92+
# A genuine error (not EAGAIN) still surfaces as a warning.
93+
_make_cgroup(fake_cgroup_root, "docker/err123", 4096)
94+
95+
def _raise_eperm(self, *args, **kwargs):
96+
raise OSError(errno.EPERM, "operation not permitted")
97+
98+
monkeypatch.setattr(Path, "write_text", _raise_eperm)
99+
with caplog.at_level(logging.DEBUG, logger=memory_pressure.log.name):
100+
memory_pressure._reclaim_container("err123")
101+
102+
assert any(
103+
r.levelno == logging.WARNING and "failed" in r.getMessage()
104+
for r in caplog.records
105+
)
106+
107+
70108
async def test_reclaim_compose_stack_reclaims_each_container(fake_cgroup_root):
71109
cgroup_a = _make_cgroup(fake_cgroup_root, "docker/aaa", 100)
72110
cgroup_b = _make_cgroup(fake_cgroup_root, "docker/bbb", 200)

0 commit comments

Comments
 (0)