Make TUI quit instant by SIGKILL-ing the sandbox container (#548)

This commit is contained in:
Ahmed Allam
2026-06-08 23:40:45 -07:00
committed by GitHub
parent 250fe2cf3e
commit 45409cef0d
2 changed files with 15 additions and 17 deletions
+5 -17
View File
@@ -751,7 +751,7 @@ class StrixTUIApp(App): # type: ignore[misc]
self.report_state.cleanup() self.report_state.cleanup()
def signal_handler(_signum: int, _frame: Any) -> None: def signal_handler(_signum: int, _frame: Any) -> None:
self._teardown_sandbox_blocking(timeout=10.0) self._fire_sandbox_cleanup()
self.report_state.cleanup(status="interrupted") self.report_state.cleanup(status="interrupted")
sys.exit(0) sys.exit(0)
@@ -1705,36 +1705,24 @@ class StrixTUIApp(App): # type: ignore[misc]
) )
async def action_custom_quit(self) -> None: async def action_custom_quit(self) -> None:
await asyncio.to_thread(self._teardown_sandbox_blocking, timeout=10.0) self._fire_sandbox_cleanup()
if self._scan_thread and self._scan_thread.is_alive(): if self._scan_thread and self._scan_thread.is_alive():
self._scan_stop_event.set() self._scan_stop_event.set()
self._scan_thread.join(timeout=2.0)
self.report_state.cleanup() self.report_state.cleanup()
self.exit() self.exit()
def _teardown_sandbox_blocking(self, *, timeout: float) -> None: def _fire_sandbox_cleanup(self) -> None:
loop = self._scan_loop loop = self._scan_loop
if loop is None or loop.is_closed(): if loop is None or loop.is_closed():
return return
run_name = self.scan_config.get("run_name") run_name = self.scan_config.get("run_name")
if not run_name: if not run_name:
return return
future = asyncio.run_coroutine_threadsafe( with contextlib.suppress(Exception):
session_manager.cleanup(run_name), asyncio.run_coroutine_threadsafe(session_manager.cleanup(run_name), loop)
loop,
)
try:
future.result(timeout=timeout)
except TimeoutError:
logger.warning(
"Sandbox cleanup timed out after %.1fs; container may still be running",
timeout,
)
except Exception:
logger.exception("Sandbox cleanup failed")
def _is_widget_safe(self, widget: Any) -> bool: def _is_widget_safe(self, widget: Any) -> bool:
try: try:
+10
View File
@@ -22,6 +22,7 @@ re-merging the parent body. Track upstream for an injection hook.
from __future__ import annotations from __future__ import annotations
import contextlib
import logging import logging
import uuid import uuid
from typing import Any from typing import Any
@@ -34,6 +35,8 @@ from agents.sandbox.sandboxes.docker import (
_manifest_requires_fuse, _manifest_requires_fuse,
_manifest_requires_sys_admin, _manifest_requires_sys_admin,
) )
from agents.sandbox.session.sandbox_session import SandboxSession
from docker import errors as docker_errors # type: ignore[import-untyped, unused-ignore]
from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore] from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore]
from docker.utils import parse_repository_tag # type: ignore[import-untyped, unused-ignore] from docker.utils import parse_repository_tag # type: ignore[import-untyped, unused-ignore]
@@ -121,3 +124,10 @@ class StrixDockerSandboxClient(DockerSandboxClient):
image, image,
) )
return container return container
async def delete(self, session: SandboxSession) -> SandboxSession:
container_id = getattr(getattr(session._inner, "state", None), "container_id", None)
if container_id:
with contextlib.suppress(docker_errors.NotFound, docker_errors.APIError):
self.docker_client.containers.get(container_id).kill()
return await super().delete(session)