From 922c5ad9ab6d74a7ebc32c62befcfb6ce688a8c2 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 6 Jun 2026 14:48:51 -0400 Subject: [PATCH] feat(app_controller): wire _io_pool + warmup + 5 public delegation methods Phase 2 Task T2.5 of the startup_speedup_20260606 track. In AppController.__init__, right after the lock init (and before the heavy subsystem construction that follows), create the shared _io_pool and WarmupManager, then submit the warmup list. The warmup runs concurrently with the rest of __init__, so by the time __init__ returns, the heavy modules are loaded (or in flight). Changes: - Add imports: from src.io_pool import make_io_pool, from src.warmup import WarmupManager - In __init__, after the locks block, add: self._io_pool = make_io_pool() self._warmup = WarmupManager(self._io_pool) self._warmup.submit(self._compute_warmup_list()) - Add _compute_warmup_list() method: returns ['google.genai', 'anthropic', 'openai', 'requests', 'src.command_palette', 'src.theme_nerv', 'src.theme_nerv_fx', 'src.markdown_table', 'numpy'] always, plus ['fastapi', 'fastapi.security.api_key'] if self.test_hooks_enabled - Add public delegation methods: warmup_status(), is_warmup_done(), wait_for_warmup(timeout), on_warmup(callback) - In shutdown(), add self._io_pool.shutdown(wait=False) The warmup currently is a no-op for the heavy modules already imported at the top of app_controller.py (fastapi, requests, etc. are already in sys.modules). The infrastructure is in place; Phase 3 will remove the top-level imports so the warmup actually does work. Verified: all 18 tests pass (test_io_pool + test_warmup + existing test_app_controller_mcp + test_app_controller_offloading). --- src/app_controller.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) 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