@@ -89,6 +89,45 @@ def _terminate_process(proc: subprocess.Popen[bytes]) -> None:
8989 pass
9090
9191
92+ def _terminate_process_async_atexit (proc : asyncio .subprocess .Process ) -> None :
93+ if proc .returncode is not None :
94+ return
95+
96+ try :
97+ if sys .platform != "win32" :
98+ os .killpg (proc .pid , signal .SIGTERM )
99+ else :
100+ proc .terminate ()
101+ except Exception :
102+ pass
103+
104+
105+ async def _terminate_process_async (proc : asyncio .subprocess .Process ) -> None :
106+ if proc .returncode is not None :
107+ return
108+
109+ try :
110+ if sys .platform != "win32" :
111+ os .killpg (proc .pid , signal .SIGTERM )
112+ else :
113+ proc .terminate ()
114+ await asyncio .wait_for (proc .wait (), timeout = 3 )
115+ return
116+ except Exception :
117+ pass
118+
119+ try :
120+ if sys .platform != "win32" :
121+ os .killpg (proc .pid , signal .SIGKILL )
122+ else :
123+ proc .kill ()
124+ finally :
125+ try :
126+ await asyncio .wait_for (proc .wait (), timeout = 3 )
127+ except Exception :
128+ pass
129+
130+
92131def _wait_ready_sync (* , base_url : str , timeout_s : float ) -> None :
93132 deadline = time .monotonic () + timeout_s
94133 with httpx .Client (timeout = 1.0 ) as client :
@@ -138,6 +177,7 @@ def __init__(
138177 self ._async_lock = asyncio .Lock ()
139178
140179 self ._proc : subprocess .Popen [bytes ] | None = None
180+ self ._async_proc : asyncio .subprocess .Process | None = None
141181 self ._base_url : str | None = None
142182 self ._atexit_registered : bool = False
143183
@@ -177,12 +217,12 @@ def ensure_running_sync(self) -> str:
177217
178218 async def ensure_running_async (self ) -> str :
179219 async with self ._async_lock :
180- if self ._proc is not None and self ._proc . poll () is None and self ._base_url is not None :
220+ if self ._async_proc is not None and self ._async_proc . returncode is None and self ._base_url is not None :
181221 return self ._base_url
182222
183223 base_url , proc = await self ._start_async ()
184224 self ._base_url = base_url
185- self ._proc = proc
225+ self ._async_proc = proc
186226 return base_url
187227
188228 def close (self ) -> None :
@@ -201,10 +241,10 @@ async def aclose(self) -> None:
201241 return
202242
203243 async with self ._async_lock :
204- if self ._proc is None :
244+ if self ._async_proc is None :
205245 return
206- _terminate_process (self ._proc )
207- self ._proc = None
246+ await _terminate_process_async (self ._async_proc )
247+ self ._async_proc = None
208248 self ._base_url = None
209249
210250 def _start_sync (self ) -> tuple [str , subprocess .Popen [bytes ]]:
@@ -246,7 +286,7 @@ def _start_sync(self) -> tuple[str, subprocess.Popen[bytes]]:
246286
247287 return base_url , proc
248288
249- async def _start_async (self ) -> tuple [str , subprocess .Popen [ bytes ] ]:
289+ async def _start_async (self ) -> tuple [str , asyncio . subprocess .Process ]:
250290 if not self ._binary_path .exists ():
251291 raise FileNotFoundError (
252292 f"Stagehand SEA binary not found at { self ._binary_path } . "
@@ -257,30 +297,22 @@ async def _start_async(self) -> tuple[str, subprocess.Popen[bytes]]:
257297 base_url = _build_base_url (host = self ._config .host , port = port )
258298 proc_env = self ._build_process_env (port = port )
259299
260- preexec_fn = None
261- creationflags = 0
262- if sys .platform != "win32" :
263- preexec_fn = os .setsid
264- else :
265- creationflags = subprocess .CREATE_NEW_PROCESS_GROUP
266-
267- proc = subprocess .Popen (
268- [str (self ._binary_path )],
300+ proc = await asyncio .create_subprocess_exec (
301+ str (self ._binary_path ),
269302 env = proc_env ,
270303 stdout = None ,
271304 stderr = None ,
272- preexec_fn = preexec_fn ,
273- creationflags = creationflags ,
305+ start_new_session = True ,
274306 )
275307
276308 if not self ._atexit_registered :
277- atexit .register (_terminate_process , proc )
309+ atexit .register (_terminate_process_async_atexit , proc )
278310 self ._atexit_registered = True
279311
280312 try :
281313 await _wait_ready_async (base_url = base_url , timeout_s = self ._config .ready_timeout_s )
282314 except Exception :
283- _terminate_process (proc )
315+ await _terminate_process_async (proc )
284316 raise
285317
286318 return base_url , proc
0 commit comments