diff --git a/CHANGELOG.md b/CHANGELOG.md index a7136f96b..f8fa4ed77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Bedrock: Correct max_tokens for llama3-8b, llama3-70b models on Bedrock. - Inspect View: Various improvements to appearance of tool calls in transcript. - Task display: Ensure that widths of progress elements are kept consistant across tasks. +- Sandboxes: Remove use of aiofiles to mitigate potential for threading deadlocks. - Bugfix: Proper handling of text find for eval raw JSON display - Bugfix: Correct handling for `--sample-id` integer comparisons. - Bugfix: Proper removal of model_args with falsey values (explicit check for `None`) diff --git a/pyproject.toml b/pyproject.toml index a796c2f82..ea79c4414 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,7 +132,6 @@ dev = [ "ruff==0.8.3", # match version specified in .pre-commit-config.yaml "textual-dev>=0.86.2", "types-PyYAML", - "types-aiofiles", "types-beautifulsoup4", "types-aioboto3", "types-boto3", diff --git a/requirements.txt b/requirements.txt index 1e82560eb..68fe38587 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -aiofiles aiohttp>=3.9.0 anyio>=4.4.0 # not directly required, pinned by Snyk to avoid a vulnerability beautifulsoup4 diff --git a/src/inspect_ai/util/_sandbox/docker/config.py b/src/inspect_ai/util/_sandbox/docker/config.py index 7241ac56e..13ef01884 100644 --- a/src/inspect_ai/util/_sandbox/docker/config.py +++ b/src/inspect_ai/util/_sandbox/docker/config.py @@ -2,8 +2,6 @@ from logging import getLogger from pathlib import Path -import aiofiles - logger = getLogger(__name__) @@ -17,7 +15,7 @@ DOCKERFILE = "Dockerfile" -async def resolve_compose_file(parent: str = "") -> str: +def resolve_compose_file(parent: str = "") -> str: # existing compose file provides all the config we need compose = find_compose_file(parent) if compose is not None: @@ -29,11 +27,11 @@ async def resolve_compose_file(parent: str = "") -> str: # dockerfile just needs a compose.yaml synthesized elif has_dockerfile(parent): - return await auto_compose_file(COMPOSE_DOCKERFILE_YAML, parent) + return auto_compose_file(COMPOSE_DOCKERFILE_YAML, parent) # otherwise provide a generic python container else: - return await auto_compose_file(COMPOSE_GENERIC_YAML, parent) + return auto_compose_file(COMPOSE_GENERIC_YAML, parent) def find_compose_file(parent: str = "") -> str | None: @@ -59,9 +57,9 @@ def is_auto_compose_file(file: str) -> bool: return os.path.basename(file) == AUTO_COMPOSE_YAML -async def ensure_auto_compose_file(file: str | None) -> None: +def ensure_auto_compose_file(file: str | None) -> None: if file is not None and is_auto_compose_file(file) and not os.path.exists(file): - await resolve_compose_file(os.path.dirname(file)) + resolve_compose_file(os.path.dirname(file)) def safe_cleanup_auto_compose(file: str | None) -> None: @@ -100,8 +98,8 @@ def safe_cleanup_auto_compose(file: str | None) -> None: """ -async def auto_compose_file(contents: str, parent: str = "") -> str: +def auto_compose_file(contents: str, parent: str = "") -> str: path = os.path.join(parent, AUTO_COMPOSE_YAML) - async with aiofiles.open(path, "w", encoding="utf-8") as f: - await f.write(contents) + with open(path, "w", encoding="utf-8") as f: + f.write(contents) return Path(path).resolve().as_posix() diff --git a/src/inspect_ai/util/_sandbox/docker/docker.py b/src/inspect_ai/util/_sandbox/docker/docker.py index 144c7efa3..97f73ca2d 100644 --- a/src/inspect_ai/util/_sandbox/docker/docker.py +++ b/src/inspect_ai/util/_sandbox/docker/docker.py @@ -5,7 +5,6 @@ from pathlib import Path, PurePosixPath from typing import Literal, Union, cast, overload -import aiofiles from typing_extensions import override from inspect_ai.util._subprocess import ExecResult @@ -403,11 +402,11 @@ async def read_file(self, file: str, text: bool = True) -> Union[str, bytes]: # read and return w/ appropriate encoding if text: - async with aiofiles.open(dest_file, "r", encoding="utf-8") as f: - return await f.read() + with open(dest_file, "r", encoding="utf-8") as f: + return f.read() else: - async with aiofiles.open(dest_file, "rb") as f: - return await f.read() + with open(dest_file, "rb") as f: + return f.read() @override async def connection(self) -> SandboxConnection: diff --git a/src/inspect_ai/util/_sandbox/docker/util.py b/src/inspect_ai/util/_sandbox/docker/util.py index d21111827..15de8c175 100644 --- a/src/inspect_ai/util/_sandbox/docker/util.py +++ b/src/inspect_ai/util/_sandbox/docker/util.py @@ -41,7 +41,7 @@ async def create( # if its a Dockerfile, then config is the auto-generated .compose.yaml if config_path and is_dockerfile(config_path.name): - config = await auto_compose_file( + config = auto_compose_file( COMPOSE_DOCKERFILE_YAML, config_path.parent.as_posix() ) @@ -51,12 +51,12 @@ async def create( # no config passed, look for 'auto-config' (compose.yaml, Dockerfile, etc.) else: - config = await resolve_compose_file() + config = resolve_compose_file() # this could be a cleanup where docker has tracked a .compose.yaml file # as part of its ConfigFiles and passed it back to us -- we in the # meantime have cleaned it up so we re-create it here as required - await ensure_auto_compose_file(config) + ensure_auto_compose_file(config) # return project return ComposeProject(name, config, env) diff --git a/src/inspect_ai/util/_sandbox/local.py b/src/inspect_ai/util/_sandbox/local.py index 20237ed0e..abbf31cfa 100644 --- a/src/inspect_ai/util/_sandbox/local.py +++ b/src/inspect_ai/util/_sandbox/local.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import Literal, Union, cast, overload -import aiofiles from typing_extensions import override from .._subprocess import ExecResult, subprocess @@ -85,11 +84,11 @@ async def write_file(self, file: str, contents: str | bytes) -> None: Path(file).parent.mkdir(parents=True, exist_ok=True) if isinstance(contents, str): - async with aiofiles.open(file, "w", encoding="utf-8") as f: - await f.write(contents) + with open(file, "w", encoding="utf-8") as f: + f.write(contents) else: - async with aiofiles.open(file, "wb") as f: - await f.write(contents) + with open(file, "wb") as f: + f.write(contents) @overload async def read_file(self, file: str, text: Literal[True] = True) -> str: ... @@ -102,11 +101,11 @@ async def read_file(self, file: str, text: bool = True) -> Union[str | bytes]: file = self._resolve_file(file) verify_read_file_size(file) if text: - async with aiofiles.open(file, "r", encoding="utf-8") as f: - return await f.read() + with open(file, "r", encoding="utf-8") as f: + return f.read() else: - async with aiofiles.open(file, "rb") as f: - return await f.read() + with open(file, "rb") as f: + return f.read() def _resolve_file(self, file: str) -> str: path = Path(file)