improved startup first frame boot
This commit is contained in:
+43
-4
@@ -801,7 +801,7 @@ class AppController:
|
||||
Owns the application state and manages background services.
|
||||
"""
|
||||
|
||||
def __init__(self, log_to_stderr: bool = True):
|
||||
def __init__(self, defer_warmup: bool = False, log_to_stderr: Optional[bool] = None):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
@@ -823,10 +823,22 @@ class AppController:
|
||||
|
||||
# --- Shared background pool + proactive warmup (startup_speedup_20260606) ---
|
||||
self._io_pool = make_io_pool()
|
||||
# Warmup progress is a diagnostic; keep stderr quiet unless explicitly asked.
|
||||
# Explicit log_to_stderr arg wins; otherwise default to the SLOP_WARMUP_DEBUG env flag.
|
||||
if log_to_stderr is None:
|
||||
log_to_stderr = bool(os.environ.get("SLOP_WARMUP_DEBUG"))
|
||||
self._warmup = WarmupManager(self._io_pool, log_to_stderr=log_to_stderr)
|
||||
# Hook warmup completion to stamp warmup_done_ts for startup_timeline().
|
||||
self._warmup.on_complete(self._on_warmup_complete_for_timeline)
|
||||
self._warmup.submit(self._compute_warmup_list())
|
||||
self._warmup_started: bool = False
|
||||
self._defer_warmup: bool = defer_warmup
|
||||
self._pending_fetch_provider: Optional[str] = None
|
||||
# The desktop GUI defers warmup until the first frame is painted (see
|
||||
# App._gui_func) so the ~2s of heavy SDK C-extension imports don't hold the
|
||||
# GIL while the window and font atlas are being created — that contention is
|
||||
# what made the window slow to appear. Headless/web/tests warm immediately.
|
||||
if not defer_warmup:
|
||||
self.start_warmup()
|
||||
|
||||
# --- Internal State ---
|
||||
self._ai_status: str = "idle"
|
||||
@@ -2132,6 +2144,13 @@ class AppController:
|
||||
"""
|
||||
[C: src/gui_2.py:App.run]
|
||||
"""
|
||||
# In the desktop GUI, model listing imports the provider SDKs (the same
|
||||
# ~2s C-extension load warmup pays for). Defer it until the first frame is
|
||||
# painted so it doesn't contend for the GIL during window creation; the
|
||||
# deferred fetch is fired from start_warmup().
|
||||
if self._defer_warmup and not self._warmup_started:
|
||||
self._pending_fetch_provider = provider
|
||||
return
|
||||
self.ai_status = "fetching models..."
|
||||
|
||||
def do_fetch() -> None:
|
||||
@@ -2213,9 +2232,26 @@ class AppController:
|
||||
"""
|
||||
return self._warmup.is_done()
|
||||
|
||||
def start_warmup(self) -> None:
|
||||
"""
|
||||
Submit the heavy-module warmup jobs to the io_pool (idempotent).
|
||||
|
||||
Separated from __init__ so the desktop GUI can call it AFTER the first
|
||||
frame is painted, keeping the ~2s of SDK C-extension imports off the GIL
|
||||
while the window is being created. Safe to call multiple times.
|
||||
"""
|
||||
if self._warmup_started:
|
||||
return
|
||||
self._warmup_started = True
|
||||
self._warmup.submit(self._compute_warmup_list())
|
||||
# Run any model fetch that was deferred while the window was being created.
|
||||
if self._pending_fetch_provider is not None:
|
||||
provider, self._pending_fetch_provider = self._pending_fetch_provider, None
|
||||
self._fetch_models(provider)
|
||||
|
||||
def wait_for_warmup(self, timeout: Optional[float] = None) -> bool:
|
||||
"""
|
||||
|
||||
|
||||
Block until warmup completes. Returns True on done, False on timeout.
|
||||
[SDM: src/app_controller.py:wait_for_warmup]
|
||||
"""
|
||||
@@ -2268,7 +2304,10 @@ class AppController:
|
||||
|
||||
def _init_ai_and_hooks(self, app: Any = None) -> None:
|
||||
from src import api_hooks
|
||||
ai_client.set_provider(self._current_provider, self._current_model)
|
||||
# validate=False: skip the live model-list lookup (network/subprocess +
|
||||
# provider-SDK import) on the main thread during startup. _fetch_models
|
||||
# corrects the model against the live list after the first frame, off-thread.
|
||||
ai_client.set_provider(self._current_provider, self._current_model, validate=False)
|
||||
if self._current_provider == "gemini_cli":
|
||||
if not ai_client._gemini_cli_adapter:
|
||||
ai_client._gemini_cli_adapter = ai_client.GeminiCliAdapter(binary_path=self.ui_gemini_cli_path)
|
||||
|
||||
Reference in New Issue
Block a user