Skip to content

Commit 8009345

Browse files
committed
Wait for SCML policy workers before decisions
1 parent cb917e4 commit 8009345

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

codeclash/arenas/scml/runtime/run_scml.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ def safe_module_name(player_name: str) -> str:
3535

3636
def load_policy_module(player_name: str, path: str):
3737
agent_dir = str(Path(path).resolve().parent)
38-
sys.path.insert(0, agent_dir)
38+
inserted_agent_dir = agent_dir not in sys.path
39+
if inserted_agent_dir:
40+
sys.path.insert(0, agent_dir)
3941
module_name = f"codeclash_scml_{safe_class_name(player_name).lower()}"
4042
spec = importlib.util.spec_from_file_location(module_name, path)
4143
if spec is None or spec.loader is None:
@@ -44,7 +46,7 @@ def load_policy_module(player_name: str, path: str):
4446
try:
4547
spec.loader.exec_module(module)
4648
finally:
47-
with contextlib.suppress(ValueError):
49+
if inserted_agent_dir:
4850
sys.path.remove(agent_dir)
4951
if not hasattr(module, "decide") or not callable(module.decide):
5052
raise RuntimeError(f"{path} must define a callable decide(observation)")
@@ -178,18 +180,20 @@ def normalize_response(response) -> tuple[ResponseType | None, str | None]:
178180
return None, f"unknown response: {response}"
179181

180182

181-
def policy_worker(command_queue: mp.Queue, result_queue: mp.Queue, player_name: str, path: str) -> None:
183+
def policy_worker(
184+
command_queue: mp.Queue, result_queue: mp.Queue, startup_queue: mp.Queue, player_name: str, path: str
185+
) -> None:
182186
try:
183187
module = load_policy_module(player_name, path)
184188
except BaseException as exc:
185-
result_queue.put(
189+
startup_queue.put(
186190
{
187-
"request_id": None,
188191
"error": f"{type(exc).__name__}: {exc}",
189192
"traceback": traceback.format_exc(limit=5),
190193
}
191194
)
192195
return
196+
startup_queue.put({"ready": True})
193197

194198
while True:
195199
try:
@@ -223,17 +227,31 @@ def __init__(self, player_name: str, path: str, *, timeout: float, max_errors: i
223227
self.policy_errors = 0
224228
self.invalid_decisions = 0
225229
self.error_samples: list[dict[str, str]] = []
230+
self.startup_error: str | None = None
226231
self._next_request_id = 0
227232
self._start_worker()
228233

229234
def _start_worker(self) -> None:
235+
self.startup_error = None
230236
ctx = mp.get_context("spawn")
231237
self.command_queue = ctx.Queue()
232238
self.result_queue = ctx.Queue()
239+
self.startup_queue = ctx.Queue()
233240
self.process = ctx.Process(
234-
target=policy_worker, args=(self.command_queue, self.result_queue, self.player_name, self.path)
241+
target=policy_worker,
242+
args=(self.command_queue, self.result_queue, self.startup_queue, self.player_name, self.path),
235243
)
236244
self.process.start()
245+
startup_timeout = max(self.timeout, 10.0)
246+
try:
247+
startup_message = self.startup_queue.get(timeout=startup_timeout)
248+
except queue.Empty:
249+
self.startup_error = f"policy import exceeded {startup_timeout}s timeout"
250+
self.close()
251+
return
252+
if "error" in startup_message:
253+
self.startup_error = startup_message["error"]
254+
self.close()
237255

238256
def _record_error(self, event: str, error: str, *, invalid: bool = False) -> None:
239257
self.policy_errors += 1
@@ -264,6 +282,11 @@ def decide(self, observation: dict[str, Any]) -> dict[str, Any]:
264282
if self.disabled:
265283
return {}
266284
event = str(observation.get("event", "unknown"))
285+
if self.startup_error is not None:
286+
self._record_error(event, self.startup_error)
287+
if not self.disabled:
288+
self.restart()
289+
return {}
267290
request_id = self._next_request_id
268291
self._next_request_id += 1
269292
self.command_queue.put({"request_id": request_id, "observation": observation})

0 commit comments

Comments
 (0)