some changes and log fix

This commit is contained in:
2026-07-30 19:58:53 +03:00
parent 68bec25f17
commit 7c6cab07fc
10 changed files with 228 additions and 61 deletions
+20 -3
View File
@@ -105,7 +105,10 @@ def _read_log_tail(path: Path, max_bytes: int = 16384) -> str:
data = handle.read()
except OSError:
return ""
return data.decode("utf-8", errors="replace").strip()
# Drop NULs: logs written by an older supervisor can carry a sparse hole from
# the pre-O_APPEND truncate bug, and a tail landing in it would otherwise turn
# an exit report (or a rolled `.prev`) into megabytes of NUL padding.
return data.replace(b"\0", b"").decode("utf-8", errors="replace").strip()
class ProcessSupervisor:
@@ -232,8 +235,17 @@ class ProcessSupervisor:
self._roll_log_to_prev(stdout_path)
self._roll_log_to_prev(stderr_path)
stdout_file = open(stdout_path, "wb")
stderr_file = open(stderr_path, "wb")
# O_APPEND ("ab"), not "wb": the child inherits these fds and keeps its own
# file offset. Without O_APPEND, the in-place truncate in
# `_roll_log_if_oversized` leaves that offset far past the new end of file,
# so the next write lands there and the kernel fills everything before it
# with a hole of NUL bytes — the log becomes unreadable and the size cap
# stops working entirely. O_APPEND makes the kernel seek to EOF atomically
# on every write, so a truncate genuinely restarts the file at offset 0.
# `_roll_log_to_prev` above already renamed any previous log away, so not
# truncating on open costs nothing.
stdout_file = open(stdout_path, "ab")
stderr_file = open(stderr_path, "ab")
try:
handle = subprocess.Popen(
command,
@@ -499,6 +511,11 @@ class ProcessSupervisor:
The child holds an open fd to this inode, so a rename would not redirect
its writes. Instead keep one rolled generation via copy-to-`.prev` and
truncate the live inode in place, freeing the allocated disk blocks.
This relies on the child's fd being opened with O_APPEND (see `_spawn`):
only then does the child resume writing at offset 0 after the truncate.
With a plain write fd it would keep writing at its stale offset, punching
a multi-hundred-megabyte NUL hole and defeating the cap.
"""
try:
if path.stat().st_size <= _LOG_MAX_BYTES: