2525
2626import os
2727import shutil
28+ import signal
2829import socket
2930import subprocess
3031import tempfile
3132import time
3233from typing import Sequence
3334
35+ # Environment variable that overrides the bundled binary location. Primarily
36+ # for development against a locally-built daemon, and for tests pointing at a
37+ # fake binary.
38+ _BIN_ENV_VAR = "BIGTABLE_ACCELERATOR_BIN"
39+
3440# Wheels ship the binary at this path relative to the `_accelerator/` package.
3541_DEFAULT_BIN_RELATIVE_PATH = "bin/accelerator"
3642
4450_SIGKILL_GRACE_SECONDS = 2.0
4551
4652
47- def _resolve_binary_path () -> str :
48- """Return the path of the bundled daemon binary, raising if it is absent."""
53+ def _resolve_binary_path (explicit_path : str | None = None ) -> str :
54+ """Resolve the daemon binary path, validating that it is a regular file.
55+
56+ Precedence: an explicit ``binary_path`` argument, then the
57+ ``BIGTABLE_ACCELERATOR_BIN`` env var, then the binary bundled in the wheel.
58+ An explicit path or env override that does not point at a regular file is a
59+ hard error (a caller who named a path meant it); a missing bundled binary
60+ reports how to supply one.
61+ """
62+ if explicit_path is not None :
63+ if not os .path .isfile (explicit_path ):
64+ raise FileNotFoundError (
65+ f"binary_path={ explicit_path !r} does not point at a regular file"
66+ )
67+ return explicit_path
68+ override = os .environ .get (_BIN_ENV_VAR )
69+ if override :
70+ if not os .path .isfile (override ):
71+ raise FileNotFoundError (
72+ f"{ _BIN_ENV_VAR } ={ override !r} does not point at a regular file"
73+ )
74+ return override
4975 bundled = os .path .join (os .path .dirname (__file__ ), _DEFAULT_BIN_RELATIVE_PATH )
5076 if not os .path .isfile (bundled ):
5177 raise FileNotFoundError (
52- "Accelerator binary not found. Install a wheel that bundles the binary."
78+ "No accelerator binary found. Set the "
79+ f"{ _BIN_ENV_VAR } env var to a daemon binary path, or install a "
80+ "wheel that bundles the binary."
5381 )
5482 return bundled
5583
@@ -76,20 +104,25 @@ def __init__(
76104 self ,
77105 cli_flags : Sequence [str ] = (),
78106 * ,
107+ binary_path : str | None = None ,
79108 startup_timeout : float = _DEFAULT_STARTUP_TIMEOUT ,
80109 ):
81110 """Resolve the binary and pick the UDS path (does not spawn anything).
82111
83112 Args:
84113 cli_flags: extra arguments appended after ``--uds-path`` when
85114 spawning the daemon (e.g. ``--project``/``--instance``).
115+ binary_path: explicit path to the daemon binary. When omitted, the
116+ path is resolved from the ``BIGTABLE_ACCELERATOR_BIN`` env var
117+ and then the binary bundled in the wheel.
86118 startup_timeout: seconds ``start()`` waits for the daemon's UDS to
87119 become connectable before raising.
88120
89121 Raises:
90- FileNotFoundError: the bundled binary is not present in this wheel.
122+ FileNotFoundError: no binary could be resolved, or an explicit
123+ ``binary_path``/env override does not point at a regular file.
91124 """
92- self ._binary_path = _resolve_binary_path ()
125+ self ._binary_path = _resolve_binary_path (binary_path )
93126 self ._cli_flags = list (cli_flags )
94127 self ._startup_timeout = startup_timeout
95128 self ._tempdir : str | None = None
@@ -139,10 +172,6 @@ def start(self) -> None:
139172 """
140173 if self ._proc is not None :
141174 raise RuntimeError ("AcceleratorDaemon.start() called twice" )
142- if not hasattr (socket , "AF_UNIX" ):
143- raise OSError (
144- "Unix domain sockets (AF_UNIX) are not supported on this platform."
145- )
146175 self ._tempdir = tempfile .mkdtemp (prefix = "bt-accel-" )
147176 self ._uds_path = os .path .join (self ._tempdir , "sock" )
148177 # Redirect the daemon's stdout/stderr to a log file rather than
@@ -157,10 +186,9 @@ def start(self) -> None:
157186 # The log file handle only needs to live long enough for Popen to dup
158187 # it into the child, so it stays local to start() rather than being an
159188 # attribute. Startup failures read the tail back from the path.
189+ log_file = open (self ._log_path , "wb" )
160190 argv = [self ._binary_path , "--uds-path" , self ._uds_path , * self ._cli_flags ]
161- log_file = None
162191 try :
163- log_file = open (self ._log_path , "wb" )
164192 self ._proc = subprocess .Popen (
165193 argv ,
166194 stdin = subprocess .PIPE ,
@@ -177,11 +205,10 @@ def start(self) -> None:
177205 # Whether or not the spawn succeeded, the parent no longer needs its
178206 # copy of the log fd: on success the child holds its own dup, and on
179207 # failure there is nothing to keep open.
180- if log_file is not None :
181- try :
182- log_file .close ()
183- except OSError :
184- pass
208+ try :
209+ log_file .close ()
210+ except OSError :
211+ pass
185212 try :
186213 self ._wait_until_ready (self ._startup_timeout )
187214 except BaseException :
@@ -205,17 +232,11 @@ def close(self) -> None:
205232 pass
206233 if not self ._wait_for_exit (_STDIN_GRACE_SECONDS ):
207234 # Step 2: SIGTERM.
208- try :
209- proc .terminate ()
210- except OSError :
211- pass
235+ proc .terminate ()
212236 if not self ._wait_for_exit (_SIGTERM_GRACE_SECONDS ):
213237 # Step 3: SIGKILL. Bounded wait so teardown can't hang
214238 # forever if the process is stuck unreapable.
215- try :
216- proc .kill ()
217- except OSError :
218- pass
239+ proc .kill ()
219240 self ._wait_for_exit (_SIGKILL_GRACE_SECONDS )
220241 finally :
221242 self ._proc = None
@@ -295,7 +316,7 @@ def _force_kill(self) -> None:
295316 if self ._proc is None or self ._proc .poll () is not None :
296317 return
297318 try :
298- self ._proc .kill ( )
319+ self ._proc .send_signal ( signal . SIGKILL )
299320 except (OSError , ProcessLookupError ):
300321 pass
301322 try :
@@ -309,4 +330,3 @@ def _cleanup_tempdir(self) -> None:
309330 shutil .rmtree (self ._tempdir , ignore_errors = True )
310331 self ._tempdir = None
311332 self ._uds_path = None
312- self ._log_path = None
0 commit comments