unsloth/studio/backend/tests/test_inference_default_models_non_blocking.py
Wasim Yousef Said 49d1fb3863
Speed up Studio startup path (#6899)
* Speed up Studio startup path

* Studio: recheck managed binary executability on preflight cache hit and ignore stale unauthenticated platform fetches

Preflight: a matching capability cache fingerprint no longer skips the
runnability check when the managed binary's executable bit was cleared
(size and mtime unchanged, since chmod bumps ctime not mtime). The cache
fast path now confirms the binary is still executable, otherwise it falls
back to the CLI help probe so preflight reports Stale and can repair,
instead of returning Ready and failing later at backend start. Adds a
regression test.

Frontend: now that first render is no longer gated on fetchDeviceType,
the initial unauthenticated health call can resolve after an
authenticated platform fetch. Guard the store so a late unauthenticated
or failed non-forced response cannot overwrite an already authoritative
device type, tunnel URL, or secure flag. Forced refreshes and the first
unauthenticated load are unaffected.

* Studio: use access(X_OK) for the preflight cache executability guard

A mode bitmask treats any execute bit as launchable, but the executable
bits can be set only for another owner or group, or be denied by an ACL,
so the current user could still hit PermissionDenied at launch and the
cached fast path would wrongly return Ready. access(X_OK) checks real
executability for the calling user, so an ownership or permission change
correctly falls back to the CLI help probe and the Stale repair path.

* Studio: ignore any stale non-forced platform fetch once authoritative

Extend the platform store guard so a non-forced health response never
overwrites an already authoritative result, not only unauthenticated
ones. With a saved token the post-render non-forced request can be
authenticated but older than a later forced refresh that already picked
up the tunnel URL and secure flag; if that earlier request resolves last
it would null those fields. Now any non-forced response is dropped once
the store holds a server-reported platform. Forced refreshes and the
first authoritative write are unaffected.

* Studio: run the managed CLI help probe before trusting the preflight cache

Restore running the managed CLI help probe before returning Ready from
the desktop capability cache, so a managed install whose venv interpreter
or a runtime dependency is broken (while path, size, mtime, and markers
are unchanged) is reported Stale for repair rather than proceeding to a
backend start that cannot spawn. The capability cache still skips the
heavier desktop-capabilities probe on a hit, so a warm cache runs one
probe instead of two. Removes the executable-access shortcut, which the
help probe now subsumes.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-07-07 18:08:07 -07:00

42 lines
1.4 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Default Chat model metadata must not block on remote Hugging Face discovery."""
from __future__ import annotations
import sys
import time
from pathlib import Path
_BACKEND = Path(__file__).resolve().parent.parent
if str(_BACKEND) not in sys.path:
sys.path.insert(0, str(_BACKEND))
from core.inference.orchestrator import InferenceOrchestrator # noqa: E402
def test_default_models_returns_static_defaults_before_top_fetch(monkeypatch):
sleep_seconds = 2.0
def _slow_fetch(self: InferenceOrchestrator) -> None:
time.sleep(sleep_seconds)
self._top_gguf_cache = ["unsloth/slow-GGUF"]
self._top_models_ready.set()
monkeypatch.setattr(InferenceOrchestrator, "_fetch_top_models", _slow_fetch)
orchestrator = InferenceOrchestrator()
started = time.monotonic()
defaults = orchestrator.default_models
elapsed = time.monotonic() - started
assert elapsed < 0.5, f"default_models blocked for {elapsed:.2f}s"
assert defaults == orchestrator._static_models
assert "unsloth/slow-GGUF" not in defaults
deadline = time.monotonic() + sleep_seconds + 5
while not orchestrator._top_models_ready.is_set() and time.monotonic() < deadline:
time.sleep(0.05)
assert "unsloth/slow-GGUF" in orchestrator.default_models