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
3 changes: 2 additions & 1 deletion pyrit/_code_execution/docker_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ async def _run_invocation_async(
cleanup_warning: str | None = None
try:
try:
process = await asyncio.wait_for(asyncio.shield(startup), timeout=self._timeout_seconds)
async with asyncio.timeout_at(deadline):
process = await asyncio.shield(startup)
except TimeoutError as error:
raise SandboxExecutionError("the Docker client did not finish starting before its deadline") from error
result = await self._wait_for_invocation_async(
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/_code_execution/test_docker_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import pytest

from pyrit._code_execution import DockerRuntimeProfile, DockerSandboxClient, SandboxExecutionError
from pyrit._code_execution.docker_client import _ClientOutput, _ContainerState, _InvocationResult
from pyrit.models.code_execution import _ProgramInvocation, _SandboxRun

if TYPE_CHECKING:
Expand Down Expand Up @@ -236,6 +237,38 @@ async def test_container_and_client_outcomes_async(
assert await asyncio.to_thread(lambda: list(tmp_path.iterdir())) == []


async def test_startup_consumes_shared_deadline_before_capture_async(client: DockerSandboxClient) -> None:
process = _process()
scope_entered = asyncio.Event()

async def start_async(*, argv: list[str]) -> MagicMock:
del argv
assert scope_entered.is_set()
return process

output = _ClientOutput(stdout=b"", stdout_truncated=False, stderr=b"", stderr_truncated=False)
container = _ContainerState(status="exited", exit_code=0, oom_killed=False)
wait = AsyncMock(return_value=_InvocationResult(output=output, container=container))

class _NoopTimeout:
async def __aenter__(self) -> None:
scope_entered.set()

async def __aexit__(self, *args: object) -> bool:
return False

with (
patch("pyrit._code_execution.docker_client.asyncio.timeout_at", return_value=_NoopTimeout()) as timeout_at,
patch.object(client, "_start_client_async", side_effect=start_async),
patch.object(client, "_wait_for_invocation_async", wait),
patch.object(client, "_control_async", _control()),
):
assert (await _run_async(client)).events[0].exit_code == 0

deadline = timeout_at.call_args.args[0]
assert wait.call_args.kwargs["deadline"] == deadline


@pytest.mark.parametrize(
("eof", "state", "error"),
[
Expand Down
Loading