mirror of
https://github.com/usestrix/strix.git
synced 2026-08-28 05:25:00 +00:00
Merge d0c926b3f0 into 717ffc8f4c
This commit is contained in:
commit
b297bba4c4
2 changed files with 44 additions and 24 deletions
|
|
@ -393,6 +393,15 @@ def _bootstrap_scan(args: argparse.Namespace) -> None:
|
|||
preflight and run preparation happen inside the TUI so the interface
|
||||
paints immediately instead of waiting on a model round trip.
|
||||
"""
|
||||
# Pre-import the heavy scan dependencies once, on the confirmed scan path.
|
||||
# This is deliberately not done before argument parsing: warm-up is
|
||||
# synchronous (see strix.llm.warmup), so running it earlier would make cold
|
||||
# --help/--version/--update and interactive setup wait on the full scan
|
||||
# import graph for no benefit.
|
||||
from strix.llm.warmup import start_import_warmup
|
||||
|
||||
start_import_warmup()
|
||||
|
||||
validate_environment()
|
||||
if not args.non_interactive:
|
||||
return
|
||||
|
|
@ -431,10 +440,6 @@ def main() -> None:
|
|||
|
||||
sys.exit(run_auth(sys.argv[2:]))
|
||||
|
||||
from strix.llm.warmup import start_import_warmup
|
||||
|
||||
start_import_warmup()
|
||||
|
||||
args = parse_arguments()
|
||||
|
||||
start_background_check()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,28 @@
|
|||
"""Background pre-import of the heavy scan dependencies.
|
||||
"""Pre-import of the heavy scan dependencies.
|
||||
|
||||
The scan engine's import graph (the agents SDK, OpenAI client, LiteLLM, the
|
||||
Caido SDK, the Docker SDK) costs seconds to import cold, but none of it is
|
||||
needed until a scan actually starts. Importing it on a daemon thread at CLI
|
||||
entry overlaps that cost with the I/O-bound startup work that always precedes
|
||||
a scan (argument parsing, Docker checks, image pull, TUI setup), so by the
|
||||
time the scan begins the modules are already in ``sys.modules``. Any thread
|
||||
that needs one of them before the warm-up finishes just blocks on the normal
|
||||
import lock, so behaviour is unchanged either way.
|
||||
needed until a scan actually starts. We import it here, once, at the start of
|
||||
the scan bootstrap so the cost is paid up front rather than on first use deep
|
||||
in the run.
|
||||
|
||||
This used to run on a background daemon thread to overlap the import cost with
|
||||
the I/O-bound startup work that precedes a scan. That is unsound: the warm-up
|
||||
imports ``strix.core.runner``, which pulls in the agents SDK, whose package
|
||||
graph has internal circular imports. CPython resolves circular imports by
|
||||
returning a *partially initialized* module to break the cycle, and that partial
|
||||
state is observable from other threads. When the warm-up thread and the main
|
||||
thread (``warm_up_llm`` imports ``agents.model_settings`` /
|
||||
``agents.models.interface``) import that graph concurrently, they intermittently
|
||||
observe each other's partial ``agents`` package and crash with
|
||||
``KeyError: 'agents'`` or "cannot import name ... from partially initialized
|
||||
module 'agents.agent_output'". This reproduces reliably on some hosts (e.g.
|
||||
WSL2). The per-module import lock does not prevent it precisely because the
|
||||
import system deliberately hands out partial modules mid-cycle.
|
||||
|
||||
Warm-up only runs on the scan path, which eagerly imports all of these modules
|
||||
anyway, so importing synchronously here adds no net work - it just removes the
|
||||
racy overlap. See https://github.com/usestrix/strix (import-warmup race).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -27,7 +42,7 @@ WARMUP_MODULES = (
|
|||
)
|
||||
|
||||
_lock = threading.Lock()
|
||||
_thread: threading.Thread | None = None
|
||||
_warmed: bool = False
|
||||
|
||||
|
||||
def _warm(modules: tuple[str, ...]) -> None:
|
||||
|
|
@ -38,18 +53,18 @@ def _warm(modules: tuple[str, ...]) -> None:
|
|||
logger.debug("Import warm-up for %r failed", name, exc_info=True)
|
||||
|
||||
|
||||
def start_import_warmup(modules: tuple[str, ...] = WARMUP_MODULES) -> threading.Thread:
|
||||
"""Start importing the heavy scan dependencies in the background, once.
|
||||
def start_import_warmup(modules: tuple[str, ...] = WARMUP_MODULES) -> None:
|
||||
"""Import the heavy scan dependencies once, synchronously.
|
||||
|
||||
``modules`` lets embedders that never touch some backends (e.g. a cloud
|
||||
runtime that has no local Docker) warm a narrower set.
|
||||
Runs on the calling thread rather than a background daemon: importing the
|
||||
agents-SDK-bearing graph concurrently with the main thread races on the
|
||||
SDK's internal circular imports (see module docstring). ``modules`` lets
|
||||
embedders that never touch some backends (e.g. a cloud runtime that has no
|
||||
local Docker) warm a narrower set.
|
||||
"""
|
||||
global _thread # noqa: PLW0603
|
||||
global _warmed # noqa: PLW0603
|
||||
with _lock:
|
||||
if _thread is not None:
|
||||
return _thread
|
||||
_thread = threading.Thread(
|
||||
target=_warm, args=(modules,), name="strix-import-warmup", daemon=True
|
||||
)
|
||||
_thread.start()
|
||||
return _thread
|
||||
if _warmed:
|
||||
return
|
||||
_warm(modules)
|
||||
_warmed = True
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue