Private
Public Access
0
0

improved startup first frame boot

This commit is contained in:
2026-06-07 01:08:31 -04:00
parent af274df837
commit 4b34f83970
8 changed files with 232 additions and 88 deletions
+43 -4
View File
@@ -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)