This commit is contained in:
2026-01-16 03:25:36 -08:00
parent 6bc3cbfe9c
commit 00bee0011c
3 changed files with 42 additions and 15 deletions

View File

@@ -16,6 +16,7 @@ import logging
import subprocess import subprocess
import shutil import shutil
import time import time
import os
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime from datetime import datetime
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
@@ -23,12 +24,18 @@ from threading import RLock
from typing import Optional, Dict, Any, List, Tuple, Set from typing import Optional, Dict, Any, List, Tuple, Set
from SYS.utils import sha256_file, expand_path from SYS.utils import sha256_file, expand_path
from SYS.logger import debug as mm_debug from SYS.logger import debug as _debug
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
WORKER_LOG_MAX_ENTRIES = 50 # Reduced from 99 to keep log size down WORKER_LOG_MAX_ENTRIES = 50 # Reduced from 99 to keep log size down
MAX_FINISHED_WORKERS = 100 # Only keep 100 finished workers globally MAX_FINISHED_WORKERS = 100 # Only keep 100 finished workers globally
# Wrapper: only emit folder DB diagnostics when MM_DEBUG=1
def mm_debug(*args, **kwargs):
if not os.environ.get("MM_DEBUG"):
return
_debug(*args, **kwargs)
# Helper: decorate DB write methods to retry transient SQLITE 'database is locked' errors # Helper: decorate DB write methods to retry transient SQLITE 'database is locked' errors
def _db_retry(max_attempts: int = 6, base_sleep: float = 0.1): def _db_retry(max_attempts: int = 6, base_sleep: float = 0.1):
def _decorator(func): def _decorator(func):

4
CLI.py
View File

@@ -823,7 +823,9 @@ class CmdletIntrospection:
normalized_arg = (arg_name or "").lstrip("-").strip().lower() normalized_arg = (arg_name or "").lstrip("-").strip().lower()
if normalized_arg in ("storage", "store"): if normalized_arg in ("storage", "store"):
backends = cls.store_choices(config, force=force) # Use cached/lightweight names for completions to avoid instantiating backends
# (instantiating backends may perform initialization such as opening folder DBs).
backends = cls.store_choices(config, force=False)
if backends: if backends:
return backends return backends

View File

@@ -222,19 +222,27 @@ class SharedArgs:
if not force and hasattr(SharedArgs, "_cached_available_stores"): if not force and hasattr(SharedArgs, "_cached_available_stores"):
return SharedArgs._cached_available_stores or [] return SharedArgs._cached_available_stores or []
# Refresh the cache # Refresh the cache. When not forcing, prefer a lightweight configured-name
SharedArgs._refresh_store_choices_cache(config) # pass to avoid instantiating backends (which may perform work such as opening DBs).
if not force:
SharedArgs._refresh_store_choices_cache(config, skip_instantiation=True)
else:
SharedArgs._refresh_store_choices_cache(config, skip_instantiation=False)
return SharedArgs._cached_available_stores or [] return SharedArgs._cached_available_stores or []
@staticmethod @staticmethod
def _refresh_store_choices_cache(config: Optional[Dict[str, Any]] = None) -> None: def _refresh_store_choices_cache(config: Optional[Dict[str, Any]] = None, skip_instantiation: bool = False) -> None:
"""Refresh the cached store choices list. Should be called once at startup. """Refresh the cached store choices list. Should be called once at startup.
This performs the actual StoreRegistry initialization check and caches the result. This performs a lightweight pass first (reads configured names only, without
Subsequent calls to get_store_choices() will use this cache. instantiating backend classes) to avoid side-effects during autocompletion or
other quick lookups. When `skip_instantiation` is False, the function will
attempt a full StoreRegistry initialization to filter out backends that failed
to initialize properly.
Args: Args:
config: Config dict. If not provided, will try to load from config module. config: Config dict. If not provided, will try to load from config module.
skip_instantiation: When True, do not instantiate backend classes; use a lightweight list only.
""" """
try: try:
if config is None: if config is None:
@@ -245,17 +253,27 @@ class SharedArgs:
SharedArgs._cached_available_stores = [] SharedArgs._cached_available_stores = []
return return
# Initialize registry once to filter disabled stores # Lightweight pass: return configured names without instantiating backends
from Store.registry import Store as StoreRegistry
try: try:
registry = StoreRegistry(config=config, suppress_debug=True)
available = registry.list_backends()
SharedArgs._cached_available_stores = available or []
except Exception:
# If registry creation fails, fallback to configured names
from Store.registry import list_configured_backend_names from Store.registry import list_configured_backend_names
SharedArgs._cached_available_stores = list_configured_backend_names(config) or [] SharedArgs._cached_available_stores = list_configured_backend_names(config) or []
except Exception:
SharedArgs._cached_available_stores = []
# If caller explicitly requested a full scan, instantiate registry to get
# only backends that actually initialized successfully.
if skip_instantiation:
return
try:
from Store.registry import Store as StoreRegistry
registry = StoreRegistry(config=config, suppress_debug=True)
available = registry.list_backends()
if available:
SharedArgs._cached_available_stores = available
except Exception:
# Keep the lightweight list if full initialization fails
pass
except Exception: except Exception:
SharedArgs._cached_available_stores = [] SharedArgs._cached_available_stores = []