mirror of
https://github.com/unslothai/unsloth.git
synced 2026-07-09 15:58:41 +00:00
Some checks are pending
Core / Core (HF=default + TRL=default) (push) Waiting to run
Core / Core (HF=4.57.6 + TRL<1) (push) Waiting to run
Core / Core (HF=latest + TRL=latest) (push) Waiting to run
Core / llama.cpp build + smoke (push) Waiting to run
Cross-platform parity / parity (macos-latest) (push) Waiting to run
Cross-platform parity / parity (windows-latest) (push) Waiting to run
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Waiting to run
MLX CI on Mac M1 / dispatch (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Security audit / pytest tests/security (push) Waiting to run
Security audit / npm provenance + new install-script diff (push) Waiting to run
Security audit / advisory audit (pip + npm + cargo) (push) Waiting to run
Security audit / pip scan-packages :: extras (push) Waiting to run
Security audit / pip scan-packages :: studio (push) Waiting to run
Security audit / pip scan-packages :: hf-stack (push) Waiting to run
Security audit / npm scan-packages (Studio frontend tarballs) (push) Waiting to run
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Waiting to run
Studio API CI / Studio API & Auth Tests (push) Waiting to run
Backend CI / (Python 3.10) (push) Waiting to run
Backend CI / (Python 3.11) (push) Waiting to run
Backend CI / (Python 3.12) (push) Waiting to run
Backend CI / (Python 3.13) (push) Waiting to run
Backend CI / Repo tests (CPU) (push) Waiting to run
Frontend CI / Frontend build + bundle sanity (push) Waiting to run
Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Studio GGUF CI / Tool calling Tests (push) Waiting to run
Studio GGUF CI / JSON, images (push) Waiting to run
Studio load-orchestrator CI / test (push) Waiting to run
Mac Studio API CI / Studio API & Auth Tests (push) Waiting to run
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Mac Studio GGUF CI / Tool calling Tests (push) Waiting to run
Mac Studio GGUF CI / JSON, images (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Waiting to run
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Waiting to run
Windows Studio UI CI / Chat UI Tests (push) Waiting to run
Windows Studio Update CI / Studio Updating Tests (push) Waiting to run
Wheel CI / Wheel build + content sanity + import smoke (push) Waiting to run
Mac Studio UI CI / Chat UI Tests (push) Waiting to run
Mac Studio Update CI / Studio Updating Tests (push) Waiting to run
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Waiting to run
Studio UI CI / Chat UI Tests (push) Waiting to run
Studio Update CI / Studio Updating Tests (push) Waiting to run
Windows Studio API CI / Studio API & Auth Tests (push) Waiting to run
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Windows Studio GGUF CI / Tool calling Tests (push) Waiting to run
Windows Studio GGUF CI / JSON, images (push) Waiting to run
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Waiting to run
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Waiting to run
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Waiting to run
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Waiting to run
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Waiting to run
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Waiting to run
--------- Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com>
120 lines
3.6 KiB
Python
120 lines
3.6 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
|
|
|
|
"""Inference dispatcher resilience.
|
|
|
|
The dispatcher thread is the sole consumer of the response queue; if a malformed
|
|
response killed it, every in-flight generation would hang forever. A bad response
|
|
must be logged and skipped, not fatal. Fakes only.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import queue
|
|
import sys
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
_BACKEND_DIR = str(Path(__file__).resolve().parent.parent)
|
|
if _BACKEND_DIR not in sys.path:
|
|
sys.path.insert(0, _BACKEND_DIR)
|
|
|
|
from core.inference.orchestrator import InferenceOrchestrator # noqa: E402
|
|
|
|
|
|
class _ScriptedQueue:
|
|
def __init__(self, items):
|
|
self._items = list(items)
|
|
|
|
def get(self, timeout = None):
|
|
if self._items:
|
|
return self._items.pop(0)
|
|
raise queue.Empty
|
|
|
|
|
|
def _dispatcher():
|
|
o = InferenceOrchestrator.__new__(InferenceOrchestrator)
|
|
o._dispatcher_stop = threading.Event()
|
|
o._mailbox_lock = threading.Lock()
|
|
o._mailboxes = {}
|
|
return o
|
|
|
|
|
|
def test_dispatcher_survives_malformed_response_and_routes_next():
|
|
o = _dispatcher()
|
|
rid = "req-1"
|
|
mbox = queue.Queue()
|
|
o._mailboxes = {rid: mbox}
|
|
# A non-dict response (resp.get -> AttributeError) must not kill the loop;
|
|
# the following valid response must still reach its mailbox.
|
|
o._resp_queue = _ScriptedQueue([12345, {"request_id": rid, "type": "token", "text": "hi"}])
|
|
|
|
t = threading.Thread(target = o._dispatcher_loop, daemon = True)
|
|
t.start()
|
|
try:
|
|
got = mbox.get(timeout = 5)
|
|
assert got["text"] == "hi", "valid response must route despite the prior bad one"
|
|
assert t.is_alive(), "dispatcher must survive a malformed response"
|
|
finally:
|
|
o._dispatcher_stop.set()
|
|
t.join(timeout = 5)
|
|
assert not t.is_alive()
|
|
|
|
|
|
def test_dispatcher_survives_mailbox_put_error():
|
|
o = _dispatcher()
|
|
rid = "req-2"
|
|
|
|
class _BadMailbox:
|
|
def put(self, _resp):
|
|
raise RuntimeError("mailbox is broken")
|
|
|
|
good = queue.Queue()
|
|
o._mailboxes = {rid: _BadMailbox(), "req-3": good}
|
|
o._resp_queue = _ScriptedQueue(
|
|
[
|
|
{"request_id": rid, "type": "token", "text": "boom"},
|
|
{"request_id": "req-3", "type": "token", "text": "ok"},
|
|
]
|
|
)
|
|
|
|
t = threading.Thread(target = o._dispatcher_loop, daemon = True)
|
|
t.start()
|
|
try:
|
|
got = good.get(timeout = 5)
|
|
assert got["text"] == "ok"
|
|
assert t.is_alive()
|
|
finally:
|
|
o._dispatcher_stop.set()
|
|
t.join(timeout = 5)
|
|
assert not t.is_alive()
|
|
|
|
|
|
def test_route_llama_streaming_async_clients_disable_proxy_env():
|
|
"""Local llama-server streaming proxies must ignore ambient HTTP_PROXY."""
|
|
source = (Path(__file__).resolve().parent.parent / "routes" / "inference.py").read_text(
|
|
encoding = "utf-8"
|
|
)
|
|
tree = ast.parse(source)
|
|
calls = []
|
|
for node in ast.walk(tree):
|
|
if not isinstance(node, ast.Call):
|
|
continue
|
|
func = node.func
|
|
if not (
|
|
isinstance(func, ast.Attribute)
|
|
and func.attr == "AsyncClient"
|
|
and isinstance(func.value, ast.Name)
|
|
and func.value.id == "httpx"
|
|
):
|
|
continue
|
|
calls.append(node)
|
|
|
|
assert len(calls) == 5
|
|
for call in calls:
|
|
assert any(
|
|
kw.arg == "trust_env" and isinstance(kw.value, ast.Constant) and kw.value.value is False
|
|
for kw in call.keywords
|
|
), f"httpx.AsyncClient at line {call.lineno} must set trust_env=False"
|