@@ -35,7 +35,9 @@ def safe_module_name(player_name: str) -> str:
3535
3636def 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