from __future__ import annotations import importlib import logging import threading import time from collections.abc import Iterable from typing import Any logger = logging.getLogger(__name__) _started: set[tuple[str, ...]] = set() _lock = threading.Lock() def _warm_imports( modules: tuple[str, ...], functions: tuple[str, ...], calls: tuple[tuple[str, tuple[Any, ...]], ...], delay_seconds: float, ) -> None: if delay_seconds > 0: time.sleep(delay_seconds) for module in modules: try: importlib.import_module(module) except Exception: logger.debug("Background preload failed for %s", module, exc_info=True) for function_path in functions: try: module_name, function_name = function_path.split(":", 1) function = getattr(importlib.import_module(module_name), function_name) function() except Exception: logger.debug( "Background preload failed for %s", function_path, exc_info=True ) for function_path, args in calls: try: module_name, function_name = function_path.split(":", 1) function = getattr(importlib.import_module(module_name), function_name) function(*args) except Exception: logger.debug( "Background preload failed for %s%r", function_path, args, exc_info=True, ) def preload_once( name: str, *, modules: Iterable[str] = (), functions: Iterable[str] = (), calls: Iterable[tuple[str, tuple[Any, ...]]] = (), delay_seconds: float = 0.25, ) -> None: """Warm small predictable costs on a daemon thread after the visible render. Keep this limited to imports and tiny metadata. Avoid model construction and full tensor loads because those can steal enough CPU or I/O to make the visible page feel slower. """ module_tuple = tuple(dict.fromkeys(modules)) function_tuple = tuple(dict.fromkeys(functions)) call_tuple = tuple((path, tuple(args)) for path, args in calls) if not module_tuple and not function_tuple and not call_tuple: return key = (name, *module_tuple, *function_tuple, repr(call_tuple)) with _lock: if key in _started: return _started.add(key) thread = threading.Thread( target=_warm_imports, args=(module_tuple, function_tuple, call_tuple, delay_seconds), name=f"persona-ui-preload-{name}", daemon=True, ) thread.start()