Skip to content

Commit 5d83b63

Browse files
committed
Add logic for cleaning up file handlers; Ignore certain assets for copy
1 parent 3d669e1 commit 5d83b63

File tree

5 files changed

+54
-10
lines changed

5 files changed

+54
-10
lines changed

codeclash/tournaments/pvp.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ def __init__(
3030
push: bool = False,
3131
keep_containers: bool = False,
3232
):
33+
metadata_file = output_dir / "metadata.json"
34+
if metadata_file.exists():
35+
raise FileExistsError(f"Metadata file already exists: {metadata_file}")
36+
3337
super().__init__(config, name="PvpTournament", output_dir=output_dir)
34-
if self.metadata_file.exists():
35-
self.logger.critical(f"Metadata file already exists: {self.metadata_file}")
36-
raise FileExistsError(f"Metadata file already exists: {self.metadata_file}")
3738
self.cleanup_on_end = cleanup
3839
self.game: CodeArena = get_arena(
3940
self.config,
@@ -213,3 +214,4 @@ def end(self) -> None:
213214
"""Save output files, clean up game resources and push agents if requested."""
214215
self._save()
215216
self.game.end(self.cleanup_on_end)
217+
self.cleanup_handlers()

codeclash/tournaments/single_player.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def end(self):
141141
"""Clean up game resources."""
142142
self._save()
143143
self.game.end(self.cleanup_on_end)
144+
self.cleanup_handlers()
144145

145146
def evaluate(self, n_repetitions: int = 3) -> None:
146147
"""Evaluate the agent's performance by

codeclash/tournaments/tournament.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import logging
12
import os
23
import time
34
import traceback
45
from pathlib import Path
56

67
from codeclash.utils.aws import get_aws_metadata
78
from codeclash.utils.environment import create_file_in_container
8-
from codeclash.utils.log import add_root_file_handler, get_logger
9+
from codeclash.utils.log import add_root_file_handler, get_logger, remove_file_handler
910

1011

1112
class AbstractTournament:
@@ -22,7 +23,7 @@ def __init__(self, config: dict, *, name: str, output_dir: Path, **kwargs):
2223
"aws": get_aws_metadata(),
2324
}
2425
self.logger = get_logger(self.name, log_path=self.local_output_dir / "tournament.log", emoji="🏆")
25-
add_root_file_handler(self.local_output_dir / "everything.log")
26+
self._root_file_handler = add_root_file_handler(self.local_output_dir / "everything.log")
2627

2728
@property
2829
def local_output_dir(self) -> Path:
@@ -33,6 +34,17 @@ def local_output_dir(self) -> Path:
3334
def get_metadata(self) -> dict:
3435
return self._metadata
3536

37+
def cleanup_handlers(self) -> None:
38+
"""Close and remove file handlers to prevent file descriptor leaks."""
39+
# Close the root file handler
40+
remove_file_handler(logging.getLogger(), self._root_file_handler)
41+
42+
# Close logger's file handlers
43+
for handler in self.logger.handlers[:]: # Copy list to avoid modification during iteration
44+
if isinstance(handler, logging.FileHandler):
45+
self.logger.removeHandler(handler)
46+
handler.close()
47+
3648
def _copy_game_log_to_agent(self, agent, round_num: int, log_output: str, dest_path: str = None) -> None:
3749
"""Copy round log to agent environment."""
3850
try:

codeclash/utils/environment.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import logging
2+
import shutil
23
import subprocess
34
import tempfile
45
from pathlib import Path
56

67
from minisweagent.environments.docker import DockerEnvironment
78

9+
# Patterns to exclude when copying between containers
10+
COPY_EXCLUDE_PATTERNS = [".git", "__pycache__"]
11+
812

913
def assert_zero_exit_code(result: dict, *, logger: logging.Logger | None = None) -> dict:
1014
if result.get("returncode", 0) != 0:
@@ -49,6 +53,15 @@ def copy_between_containers(
4953
f"Failed to copy from {src_container.container_id} to local temp: {result_src.stdout}{result_src.stderr}"
5054
)
5155

56+
# Remove excluded patterns
57+
for pattern in COPY_EXCLUDE_PATTERNS:
58+
excluded_path = temp_path / pattern
59+
if excluded_path.exists():
60+
if excluded_path.is_dir():
61+
shutil.rmtree(excluded_path)
62+
else:
63+
excluded_path.unlink()
64+
5265
# Ensure destination folder exists
5366
assert_zero_exit_code(dest_container.execute(f"mkdir -p {Path(dest_path).parent}"))
5467

codeclash/utils/log.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ def format(self, record: logging.LogRecord) -> str:
6464
return capture.get().rstrip()
6565

6666

67-
def add_file_handler(logger: logging.Logger, log_path: Path) -> None:
68-
"""Add a file handler to the logger with standard formatting."""
67+
def add_file_handler(logger: logging.Logger, log_path: Path) -> logging.FileHandler:
68+
"""Add a file handler to the logger with standard formatting.
69+
70+
Returns:
71+
The FileHandler that was added (for later cleanup).
72+
"""
6973
log_path.parent.mkdir(parents=True, exist_ok=True)
7074
file_handler = logging.FileHandler(log_path)
7175
file_handler.setLevel(_FILE_LEVEL)
@@ -78,11 +82,23 @@ def add_file_handler(logger: logging.Logger, log_path: Path) -> None:
7882
file_handler.setFormatter(file_formatter)
7983

8084
logger.addHandler(file_handler)
85+
return file_handler
86+
87+
88+
def add_root_file_handler(log_path: Path) -> logging.FileHandler:
89+
"""Add a file handler to the root logger to capture all log messages.
90+
91+
Returns:
92+
The FileHandler that was added (for later cleanup).
93+
"""
94+
return add_file_handler(logging.getLogger(), log_path)
8195

8296

83-
def add_root_file_handler(log_path: Path) -> None:
84-
"""Add a file handler to the root logger to capture all log messages."""
85-
add_file_handler(logging.getLogger(), log_path)
97+
def remove_file_handler(logger: logging.Logger, handler: logging.FileHandler) -> None:
98+
"""Remove and close a file handler from the logger."""
99+
if handler in logger.handlers:
100+
logger.removeHandler(handler)
101+
handler.close()
86102

87103

88104
def get_logger(name: str, *, emoji: str = "", log_path: Path | None = None) -> logging.Logger:

0 commit comments

Comments
 (0)