@@ -26,15 +26,17 @@ def safe_module_name(player_name: str) -> str:
2626
2727def 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
7678def 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