3030import subprocess
3131import tempfile
3232import time
33- import typing
3433from typing import Sequence
3534
3635# Environment variable that overrides the bundled binary location. Primarily
5150_SIGKILL_GRACE_SECONDS = 2.0
5251
5352
54- def _default_binary_path () -> str | None :
55- bundled = os .path .join (os .path .dirname (__file__ ), _DEFAULT_BIN_RELATIVE_PATH )
56- return bundled if os .path .isfile (bundled ) else None
57-
58-
5953def _resolve_binary_path (explicit_path : str | None = None ) -> str :
6054 """Resolve the daemon binary path, validating that it is a regular file.
6155
@@ -78,8 +72,8 @@ def _resolve_binary_path(explicit_path: str | None = None) -> str:
7872 f"{ _BIN_ENV_VAR } ={ override !r} does not point at a regular file"
7973 )
8074 return override
81- bundled = _default_binary_path ( )
82- if bundled is None :
75+ bundled = os . path . join ( os . path . dirname ( __file__ ), _DEFAULT_BIN_RELATIVE_PATH )
76+ if not os . path . isfile ( bundled ) :
8377 raise FileNotFoundError (
8478 "No accelerator binary found. Set the "
8579 f"{ _BIN_ENV_VAR } env var to a daemon binary path, or install a "
@@ -134,7 +128,6 @@ def __init__(
134128 self ._tempdir : str | None = None
135129 self ._uds_path : str | None = None
136130 self ._log_path : str | None = None
137- self ._log_file : "typing.IO[bytes] | None" = None
138131 self ._proc : subprocess .Popen [bytes ] | None = None
139132
140133 def __enter__ (self ) -> "AcceleratorDaemon" :
@@ -190,13 +183,16 @@ def start(self) -> None:
190183 # stays a PIPE — closing it is how close() signals the daemon to shut
191184 # down.
192185 self ._log_path = os .path .join (self ._tempdir , "daemon.log" )
193- self ._log_file = open (self ._log_path , "wb" )
186+ # The log file handle only needs to live long enough for Popen to dup
187+ # it into the child, so it stays local to start() rather than being an
188+ # attribute. Startup failures read the tail back from the path.
189+ log_file = open (self ._log_path , "wb" )
194190 argv = [self ._binary_path , "--uds-path" , self ._uds_path , * self ._cli_flags ]
195191 try :
196192 self ._proc = subprocess .Popen (
197193 argv ,
198194 stdin = subprocess .PIPE ,
199- stdout = self . _log_file ,
195+ stdout = log_file ,
200196 stderr = subprocess .STDOUT ,
201197 close_fds = True ,
202198 )
@@ -208,11 +204,13 @@ def start(self) -> None:
208204 finally :
209205 # Whether or not the spawn succeeded, the parent no longer needs its
210206 # copy of the log fd: on success the child holds its own dup, and on
211- # failure there is nothing to keep open. Startup failures read the
212- # tail back from the path.
213- self ._close_log_file ()
207+ # failure there is nothing to keep open.
208+ try :
209+ log_file .close ()
210+ except OSError :
211+ pass
214212 try :
215- self ._wait_until_ready ()
213+ self ._wait_until_ready (self . _startup_timeout )
216214 except BaseException :
217215 self ._force_kill ()
218216 self ._cleanup_tempdir ()
@@ -242,13 +240,17 @@ def close(self) -> None:
242240 self ._wait_for_exit (_SIGKILL_GRACE_SECONDS )
243241 finally :
244242 self ._proc = None
245- self ._close_log_file ()
246243 self ._cleanup_tempdir ()
247244
248- def _wait_until_ready (self ) -> None :
245+ def _wait_until_ready (self , timeout : float ) -> None :
246+ """Poll until the UDS accepts a connection, the child dies, or timeout.
247+
248+ Raises RuntimeError if the daemon exits during startup or does not
249+ become connectable within ``timeout`` seconds.
250+ """
249251 if self ._proc is None or self ._uds_path is None :
250252 raise RuntimeError ("AcceleratorDaemon._wait_until_ready() before spawn" )
251- deadline = time .monotonic () + self . _startup_timeout
253+ deadline = time .monotonic () + timeout
252254 while time .monotonic () < deadline :
253255 exit_code = self ._proc .poll ()
254256 if exit_code is not None :
@@ -269,7 +271,7 @@ def _wait_until_ready(self) -> None:
269271 log_tail = self ._read_log_tail ()
270272 raise RuntimeError (
271273 "Accelerator daemon did not become ready within "
272- f"{ self . _startup_timeout } s. log: { log_tail !r} "
274+ f"{ timeout } s. log: { log_tail !r} "
273275 )
274276
275277 def _read_log_tail (self , max_bytes : int = 4096 ) -> str :
@@ -296,15 +298,11 @@ def _read_log_tail(self, max_bytes: int = 4096) -> str:
296298 return ""
297299 return data .decode ("utf-8" , errors = "replace" )
298300
299- def _close_log_file (self ) -> None :
300- if self ._log_file is not None :
301- try :
302- self ._log_file .close ()
303- except OSError :
304- pass
305- self ._log_file = None
306-
307301 def _wait_for_exit (self , timeout : float ) -> bool :
302+ """Wait up to ``timeout`` seconds for the child to exit.
303+
304+ Returns True if it has exited (or there is no child), False on timeout.
305+ """
308306 if self ._proc is None :
309307 return True
310308 try :
@@ -314,6 +312,7 @@ def _wait_for_exit(self, timeout: float) -> bool:
314312 return False
315313
316314 def _force_kill (self ) -> None :
315+ """SIGKILL the child and reap it; a no-op if it is already gone."""
317316 if self ._proc is None or self ._proc .poll () is not None :
318317 return
319318 try :
@@ -326,6 +325,7 @@ def _force_kill(self) -> None:
326325 pass
327326
328327 def _cleanup_tempdir (self ) -> None :
328+ """Remove the tempdir holding the socket and log; clear derived paths."""
329329 if self ._tempdir is not None and os .path .isdir (self ._tempdir ):
330330 shutil .rmtree (self ._tempdir , ignore_errors = True )
331331 self ._tempdir = None
0 commit comments