Read the whole bwrap info object before parsing the child pid
bwrap writes --info-fd JSON in several writes. A single os.read could
return only '{"child-pid": N' and fail with a JSON error, observed
reproducibly on railiance01 for the rebuilt owner runtime. Read until a
complete object or EOF within the existing 10 s bound; a truncated object
at EOF is still refused. 207 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 272244@bnt-lap001
Assistant-Session: c8962fa7-b290-47df-865f-403ddb6c77e9
This commit is contained in:
parent
444611586c
commit
221126cb77
2 changed files with 73 additions and 7 deletions
|
|
@ -113,14 +113,31 @@ class BwrapExtension(SandboxExtension):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _read_child_pid(proc: subprocess.Popen, info_fd: int) -> int:
|
def _read_child_pid(proc: subprocess.Popen, info_fd: int) -> int:
|
||||||
ready, _, _ = select.select([info_fd], [], [], 10)
|
# bwrap writes the info object in several writes; one read can return
|
||||||
|
# only its first line. Read until EOF or a complete object, bounded.
|
||||||
|
deadline = time.monotonic() + 10
|
||||||
|
raw = b""
|
||||||
|
info = None
|
||||||
|
while info is None:
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
ready, _, _ = select.select([info_fd], [], [], max(remaining, 0))
|
||||||
if not ready:
|
if not ready:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
raise RuntimeError("timed out waiting for bwrap namespace child pid")
|
raise RuntimeError("timed out waiting for bwrap namespace child pid")
|
||||||
raw = os.read(info_fd, 16_384)
|
chunk = os.read(info_fd, 16_384)
|
||||||
|
raw += chunk
|
||||||
|
if len(raw) > 16_384:
|
||||||
|
proc.kill()
|
||||||
|
raise RuntimeError("bwrap did not report a valid namespace child pid")
|
||||||
try:
|
try:
|
||||||
child_pid = int(json.loads(raw)["child-pid"])
|
info = json.loads(raw)
|
||||||
except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
|
except json.JSONDecodeError as exc:
|
||||||
|
if not chunk:
|
||||||
|
proc.kill()
|
||||||
|
raise RuntimeError("bwrap did not report a valid namespace child pid") from exc
|
||||||
|
try:
|
||||||
|
child_pid = int(info["child-pid"])
|
||||||
|
except (KeyError, TypeError, ValueError) as exc:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
raise RuntimeError("bwrap did not report a valid namespace child pid") from exc
|
raise RuntimeError("bwrap did not report a valid namespace child pid") from exc
|
||||||
if child_pid <= 0:
|
if child_pid <= 0:
|
||||||
|
|
|
||||||
|
|
@ -362,3 +362,52 @@ def test_runner_bounds_output_and_normalizes_timeout(tmp_path) -> None:
|
||||||
assert result["stdout"] == "123"
|
assert result["stdout"] == "123"
|
||||||
assert result["stderr"] == "abc"
|
assert result["stderr"] == "abc"
|
||||||
assert result["output_truncated"] is True
|
assert result["output_truncated"] is True
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeProc:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.killed = False
|
||||||
|
|
||||||
|
def kill(self) -> None:
|
||||||
|
self.killed = True
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_child_pid_accepts_info_split_across_writes() -> None:
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
read_fd, write_fd = os.pipe()
|
||||||
|
|
||||||
|
def writer() -> None:
|
||||||
|
os.write(write_fd, b'{\n "child-pid": 1234567')
|
||||||
|
time.sleep(0.05)
|
||||||
|
os.write(write_fd, b',\n "cgroup-namespace": 4026531835\n}\n')
|
||||||
|
os.close(write_fd)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=writer)
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
proc = _FakeProc()
|
||||||
|
assert BwrapExtension._read_child_pid(proc, read_fd) == 1234567
|
||||||
|
assert not proc.killed
|
||||||
|
finally:
|
||||||
|
thread.join()
|
||||||
|
os.close(read_fd)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_child_pid_refuses_truncated_info_at_eof() -> None:
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
read_fd, write_fd = os.pipe()
|
||||||
|
os.write(write_fd, b'{\n "child-pid": 1234567')
|
||||||
|
os.close(write_fd)
|
||||||
|
proc = _FakeProc()
|
||||||
|
try:
|
||||||
|
with pytest.raises(RuntimeError, match="valid namespace child pid"):
|
||||||
|
BwrapExtension._read_child_pid(proc, read_fd)
|
||||||
|
assert proc.killed
|
||||||
|
finally:
|
||||||
|
os.close(read_fd)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue