Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions apodex/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def set_active_strategy(strategy: Strategy) -> None:

# ── execution ────────────────────────────────────────────────────────────

_bwrap_sandbox = None # one jail per process; commands are cheap, setup is not
_bwrap_sandbox: tuple[str, Any] | None = None # one-entry cache keyed by workspace


def _get_bwrap_sandbox(cwd: str) -> Any:
Expand All @@ -223,11 +223,14 @@ def _get_bwrap_sandbox(cwd: str) -> Any:
into it.
"""
global _bwrap_sandbox
if _bwrap_sandbox is None:
real = str(Path(cwd).expanduser().resolve())
if _bwrap_sandbox is None or _bwrap_sandbox[0] != real:
from plugins.tools._sandbox import BwrapSandbox
real = str(Path(cwd).expanduser().resolve())
_bwrap_sandbox = BwrapSandbox(workspace=real, binds=((real, real, False),))
return _bwrap_sandbox
sandbox = BwrapSandbox(workspace=real, binds=((real, real, False),))
if _bwrap_sandbox is not None:
_bwrap_sandbox[1].kill()
_bwrap_sandbox = (real, sandbox)
return _bwrap_sandbox[1]
Comment thread
zhanghanduo marked this conversation as resolved.


async def run_shell(
Expand Down
33 changes: 32 additions & 1 deletion apodex/tests/test_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from pathlib import Path

from apodex import cli, docker
from apodex import cli, docker, sandbox
from apodex.native import prepare_native_runtime
from apodex.sandbox import BWRAP, CONTAINER, NATIVE, Strategy, resolve_strategy
from plugins.tools._sandbox import resolve_runtime_path
Expand Down Expand Up @@ -168,6 +168,37 @@ def test_configured_bwrap_backend_remains_explicit(monkeypatch) -> None:
assert strategy.reason == "SANDBOX_BACKEND=bwrap"


def test_bwrap_sandbox_rebuilds_when_workspace_changes(
tmp_path, monkeypatch,
) -> None:
class FakeBwrapSandbox:
def __init__(self, *, workspace, binds):
self.workspace = workspace
self.binds = binds
self.killed = False

def kill(self):
self.killed = True

first_workspace = tmp_path / "first"
second_workspace = tmp_path / "second"
first_workspace.mkdir()
second_workspace.mkdir()
monkeypatch.setattr("plugins.tools._sandbox.BwrapSandbox", FakeBwrapSandbox)
monkeypatch.setattr(sandbox, "_bwrap_sandbox", None)

first = sandbox._get_bwrap_sandbox(str(first_workspace))
same = sandbox._get_bwrap_sandbox(str(first_workspace / ".." / "first"))
second = sandbox._get_bwrap_sandbox(str(second_workspace))

assert same is first
assert second is not first
assert first.killed is True
assert second.killed is False
assert second.workspace == str(second_workspace.resolve())
assert second.binds == ((str(second_workspace.resolve()),) * 2 + (False,),)


def test_macos_falls_back_to_native_when_docker_is_unavailable(
tmp_path, monkeypatch, capsys,
) -> None:
Expand Down
Loading