Skip to content

Commit b2e233f

Browse files
devin-ai-integration[bot]bot_apk
andcommitted
fix(cdk): add error handling to _read_memory() for graceful degradation
Co-Authored-By: bot_apk <apk@cognition.ai>
1 parent 8d059cf commit b2e233f

2 files changed

Lines changed: 57 additions & 16 deletions

File tree

airbyte_cdk/utils/memory_monitor.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,35 @@ def _read_memory(self) -> Optional[tuple[int, int]]:
6868
"""Read current memory usage and limit from cgroup files.
6969
7070
Returns a tuple of (usage_bytes, limit_bytes) or None if unavailable.
71+
Best-effort: failures to read memory info never crash a sync.
7172
"""
7273
if self._cgroup_version is None:
7374
return None
7475

75-
if self._cgroup_version == 2:
76-
usage_path = _CGROUP_V2_CURRENT
77-
limit_path = _CGROUP_V2_MAX
78-
else:
79-
usage_path = _CGROUP_V1_USAGE
80-
limit_path = _CGROUP_V1_LIMIT
76+
try:
77+
if self._cgroup_version == 2:
78+
usage_path = _CGROUP_V2_CURRENT
79+
limit_path = _CGROUP_V2_MAX
80+
else:
81+
usage_path = _CGROUP_V1_USAGE
82+
limit_path = _CGROUP_V1_LIMIT
8183

82-
usage_text = usage_path.read_text().strip()
83-
limit_text = limit_path.read_text().strip()
84+
limit_text = limit_path.read_text().strip()
85+
# cgroup v2 memory.max can be the literal string "max" (unlimited)
86+
if limit_text == "max":
87+
return None
8488

85-
# cgroup v2 memory.max can be the literal string "max" (unlimited)
86-
if limit_text == "max":
87-
return None
89+
usage_bytes = int(usage_path.read_text().strip())
90+
limit_bytes = int(limit_text)
8891

89-
usage_bytes = int(usage_text)
90-
limit_bytes = int(limit_text)
92+
if limit_bytes <= 0:
93+
return None
9194

92-
if limit_bytes <= 0:
95+
return usage_bytes, limit_bytes
96+
except (OSError, ValueError):
97+
logger.debug("Failed to read cgroup memory files; skipping memory check.")
9398
return None
9499

95-
return usage_bytes, limit_bytes
96-
97100
def check_memory_usage(self) -> None:
98101
"""Check memory usage against thresholds.
99102

unit_tests/utils/test_memory_monitor.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,44 @@ def mock_read_text(self: Path) -> str:
264264
with pytest.raises(MemoryLimitExceeded):
265265
monitor.check_memory_usage()
266266

267+
def test_malformed_cgroup_file_degrades_gracefully(self) -> None:
268+
"""Malformed cgroup files should not crash the sync."""
269+
270+
def mock_exists(self: Path) -> bool:
271+
return self in (_CGROUP_V2_CURRENT, _CGROUP_V2_MAX)
272+
273+
def mock_read_text(self: Path) -> str:
274+
return "not_a_number\n"
275+
276+
with patch.object(Path, "exists", mock_exists):
277+
monitor = MemoryMonitor()
278+
279+
with patch.object(Path, "read_text", mock_read_text):
280+
# Should not raise — degrades gracefully
281+
monitor.check_memory_usage()
282+
283+
assert not monitor._warning_emitted
284+
assert not monitor._critical_raised
285+
286+
def test_os_error_degrades_gracefully(self) -> None:
287+
"""OSError reading cgroup files should not crash the sync."""
288+
289+
def mock_exists(self: Path) -> bool:
290+
return self in (_CGROUP_V2_CURRENT, _CGROUP_V2_MAX)
291+
292+
def mock_read_text(self: Path) -> str:
293+
raise OSError("Permission denied")
294+
295+
with patch.object(Path, "exists", mock_exists):
296+
monitor = MemoryMonitor()
297+
298+
with patch.object(Path, "read_text", mock_read_text):
299+
# Should not raise — degrades gracefully
300+
monitor.check_memory_usage()
301+
302+
assert not monitor._warning_emitted
303+
assert not monitor._critical_raised
304+
267305

268306
class TestMemoryLimitExceeded:
269307
"""Tests for the MemoryLimitExceeded exception."""

0 commit comments

Comments
 (0)