fix: Workaround against docker exec_run not flushing stdout
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import select as _select
|
||||
import tarfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
import docker
|
||||
@@ -12,9 +12,6 @@ import docker.errors
|
||||
from loguru import logger
|
||||
|
||||
|
||||
_original_poll = getattr(_select, "poll", None)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import docker.models.containers
|
||||
|
||||
@@ -267,30 +264,41 @@ class DockerSandbox:
|
||||
if self._container is None:
|
||||
return 1, "Sandbox container is not running."
|
||||
|
||||
wrapped = [
|
||||
"timeout",
|
||||
"--kill-after",
|
||||
f"{kill_grace}s",
|
||||
f"{timeout}s",
|
||||
"bash",
|
||||
"-c",
|
||||
command,
|
||||
]
|
||||
# docker-py's exec_run reads command output off a hijacked raw socket
|
||||
# and intermittently returns nothing because bytes that http.client
|
||||
# already buffered while parsing the response headers are stranded and
|
||||
# never read (https://github.com/docker/docker-py/issues/2042,
|
||||
# https://github.com/docker/docker-py/issues/3332). To avoid that path
|
||||
# entirely we redirect combined output to a file and read it back via
|
||||
# get_archive (a normal HTTP body), which is reliable on every
|
||||
# transport. The exit code from exec_run is unaffected by the bug.
|
||||
out_path = f"/tmp/.sandbox-exec-{uuid.uuid4().hex}"
|
||||
# The command is passed via an env var so the outer shell never has to
|
||||
# quote-escape it; "$SANDBOX_CMD" reaches the inner bash -c verbatim.
|
||||
runner = (
|
||||
f"timeout --kill-after={kill_grace}s {timeout}s "
|
||||
f'bash -c "$SANDBOX_CMD" > {out_path} 2>&1'
|
||||
)
|
||||
kwargs = {}
|
||||
if self._working_dir is not None:
|
||||
kwargs["workdir"] = self._working_dir
|
||||
|
||||
try:
|
||||
exit_code, output = self._container.exec_run(
|
||||
wrapped,
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
demux=False,
|
||||
exit_code, _ = self._container.exec_run(
|
||||
["bash", "-c", runner],
|
||||
environment={"SANDBOX_CMD": command},
|
||||
**kwargs,
|
||||
)
|
||||
text = (output or b"").decode("utf-8", errors="replace")
|
||||
if exit_code in (124, 137):
|
||||
return exit_code, text + f"\n[command timed out after {timeout}s]"
|
||||
return exit_code, text
|
||||
except Exception as exc:
|
||||
return 1, f"Error running command in container: {exc}"
|
||||
|
||||
try:
|
||||
text = self.read_file(out_path).decode("utf-8", errors="replace")
|
||||
except FileNotFoundError:
|
||||
text = ""
|
||||
finally:
|
||||
self._container.exec_run(["rm", "-f", out_path])
|
||||
|
||||
if exit_code in (124, 137):
|
||||
return exit_code, text + f"\n[command timed out after {timeout}s]"
|
||||
return exit_code, text
|
||||
|
||||
Reference in New Issue
Block a user