diff --git a/src/app_controller.py b/src/app_controller.py index 5c842204..a0d28a92 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -41,7 +41,9 @@ from src import tool_presets from src.context_presets import ContextPresetManager from src.file_cache import ASTParser +from src.io_pool import make_io_pool from src.models import GenerateRequest, ConfirmRequest +from src.warmup import WarmupManager def parse_symbols(text: str) -> list[str]: @@ -810,6 +812,11 @@ class AppController: self._rag_engine_lock: threading.Lock = threading.Lock() self._project_switch_lock: threading.Lock = threading.Lock() + # --- Shared background pool + proactive warmup (startup_speedup_20260606) --- + self._io_pool = make_io_pool() + self._warmup = WarmupManager(self._io_pool) + self._warmup.submit(self._compute_warmup_list()) + # --- Internal State --- self._ai_status: str = "idle" self._mma_status: str = "idle" @@ -2080,6 +2087,64 @@ class AppController: self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True) self._loop_thread.start() + def _compute_warmup_list(self) -> list[str]: + """ + + Returns the list of modules to warm on the _io_pool at startup. + [SDM: src/app_controller.py:_compute_warmup_list] + """ + modules: list[str] = [ + "google.genai", + "anthropic", + "openai", + "requests", + "src.command_palette", + "src.theme_nerv", + "src.theme_nerv_fx", + "src.markdown_table", + "numpy", + ] + if getattr(self, "test_hooks_enabled", False): + modules.extend([ + "fastapi", + "fastapi.security.api_key", + ]) + return modules + + def warmup_status(self) -> dict: + """ + + Snapshot of the warmup progress. {pending, completed, failed}. + Cheap (lock-guarded copy). Polled by the GUI status indicator. + [SDM: src/app_controller.py:warmup_status] + """ + return self._warmup.status() + + def is_warmup_done(self) -> bool: + """ + + True once all warmup jobs have completed (or failed). + [SDM: src/app_controller.py:is_warmup_done] + """ + return self._warmup.is_done() + + 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] + """ + return self._warmup.wait(timeout=timeout) + + def on_warmup_complete(self, callback: Callable[[dict], None]) -> None: + """ + + Register a callback for warmup completion. If already done, fires + immediately on the calling thread. + [SDM: src/app_controller.py:on_warmup_complete] + """ + self._warmup.on_complete(callback) + def shutdown(self) -> None: """ @@ -2094,6 +2159,8 @@ class AppController: self.event_queue.put("shutdown", None) if self._loop_thread and self._loop_thread.is_alive(): self._loop_thread.join(timeout=2.0) + if hasattr(self, "_io_pool") and self._io_pool is not None: + self._io_pool.shutdown(wait=False) def _init_ai_and_hooks(self, app: Any = None) -> None: from src import api_hooks