Skip to content

Commit 0b1a7fe

Browse files
committed
Wait for CybORG policy workers before decisions
1 parent 7ea5b6e commit 0b1a7fe

1 file changed

Lines changed: 37 additions & 6 deletions

File tree

codeclash/arenas/cyborg/runtime/run_cyborg.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@ def safe_module_name(player_name: str) -> str:
2626

2727
def load_policy_module(player_name: str, path: str):
2828
agent_dir = str(Path(path).resolve().parent)
29-
sys.path.insert(0, agent_dir)
29+
inserted_agent_dir = agent_dir not in sys.path
30+
if inserted_agent_dir:
31+
sys.path.insert(0, agent_dir)
3032
spec = importlib.util.spec_from_file_location(safe_module_name(player_name), path)
3133
if spec is None or spec.loader is None:
3234
raise RuntimeError(f"Could not load module spec from {path}")
3335
module = importlib.util.module_from_spec(spec)
3436
try:
3537
spec.loader.exec_module(module)
3638
finally:
37-
with contextlib.suppress(ValueError):
39+
if inserted_agent_dir:
3840
sys.path.remove(agent_dir)
3941
if not hasattr(module, "decide") or not callable(module.decide):
4042
raise RuntimeError(f"{path} must define a callable decide(observation, action_space)")
@@ -74,19 +76,24 @@ def normalize_action(action, action_space) -> tuple[int, str | None]:
7476

7577

7678
def policy_worker(
77-
command_queue: mp.Queue, result_queue: mp.Queue, player_name: str, agent_name: str, path: str
79+
command_queue: mp.Queue,
80+
result_queue: mp.Queue,
81+
startup_queue: mp.Queue,
82+
player_name: str,
83+
agent_name: str,
84+
path: str,
7885
) -> None:
7986
try:
8087
module = load_policy_module(player_name, path)
8188
except BaseException as exc:
82-
result_queue.put(
89+
startup_queue.put(
8390
{
84-
"request_id": None,
8591
"error": f"{type(exc).__name__}: {exc}",
8692
"traceback": traceback.format_exc(limit=5),
8793
}
8894
)
8995
return
96+
startup_queue.put({"ready": True})
9097

9198
while True:
9299
try:
@@ -118,18 +125,38 @@ def __init__(self, player_name: str, agent_name: str, path: str, *, timeout: flo
118125
self.errors: list[dict] = []
119126
self.invalid_actions = 0
120127
self.decisions = 0
128+
self.startup_error: str | None = None
121129
self._next_request_id = 0
122130
self._start_worker()
123131

124132
def _start_worker(self) -> None:
133+
self.startup_error = None
125134
ctx = mp.get_context("spawn")
126135
self.command_queue = ctx.Queue()
127136
self.result_queue = ctx.Queue()
137+
self.startup_queue = ctx.Queue()
128138
self.process = ctx.Process(
129139
target=policy_worker,
130-
args=(self.command_queue, self.result_queue, self.player_name, self.agent_name, self.path),
140+
args=(
141+
self.command_queue,
142+
self.result_queue,
143+
self.startup_queue,
144+
self.player_name,
145+
self.agent_name,
146+
self.path,
147+
),
131148
)
132149
self.process.start()
150+
startup_timeout = max(self.timeout, 10.0)
151+
try:
152+
startup_message = self.startup_queue.get(timeout=startup_timeout)
153+
except queue.Empty:
154+
self.startup_error = f"policy import exceeded {startup_timeout}s timeout"
155+
self.close()
156+
return
157+
if "error" in startup_message:
158+
self.startup_error = startup_message["error"]
159+
self.close()
133160

134161
def close(self) -> None:
135162
with contextlib.suppress(Exception):
@@ -147,6 +174,10 @@ def restart(self) -> None:
147174
self._start_worker()
148175

149176
def decide(self, observation, action_space) -> int:
177+
if self.startup_error is not None:
178+
self.errors.append({"agent": self.agent_name, "error": self.startup_error})
179+
self.restart()
180+
return DEFAULT_ACTION
150181
request_id = self._next_request_id
151182
self._next_request_id += 1
152183
self.command_queue.put(

0 commit comments

Comments
 (0)