Fix Studio re-exec compatibility
This commit is contained in:
parent
506cdc6c8a
commit
36f896a528
2 changed files with 63 additions and 17 deletions
|
|
@ -106,6 +106,13 @@ API_KEY_PBKDF2_SALT_KEY = "api_key_pbkdf2_salt"
|
|||
DESKTOP_SECRET_HASH_KEY = "desktop_secret_hash"
|
||||
DESKTOP_SECRET_CREATED_AT_KEY = "desktop_secret_created_at"
|
||||
PBKDF2_ITERATIONS = 100_000
|
||||
_START_API_KEY_MARKER_ENV = "_UNSLOTH_START_API_KEY_MARKER"
|
||||
|
||||
|
||||
def _consume_start_api_key_marker_env() -> bool:
|
||||
"""Consume the one-shot readiness marker passed across a Studio re-exec."""
|
||||
return os.environ.pop(_START_API_KEY_MARKER_ENV, None) == "1"
|
||||
|
||||
|
||||
# __file__ is unsloth_cli/commands/studio.py -- two parents up is the package root
|
||||
# (either site-packages or the repo root for editable installs).
|
||||
|
|
@ -1792,6 +1799,12 @@ def run(
|
|||
unsloth studio run --model some-model --chat-template-file /path/to/tpl.jinja
|
||||
unsloth studio run --model unsloth/Qwen3-27B-GGUF --gguf-variant Q8_0 --tensor-parallel
|
||||
"""
|
||||
# A newer outer CLI can re-exec into an older Studio venv. Pass this
|
||||
# internal signal through the environment so an older child ignores it
|
||||
# instead of treating an unknown CLI option as a llama-server argument.
|
||||
inherited_start_api_key_marker = _consume_start_api_key_marker_env()
|
||||
start_api_key_marker = start_api_key_marker or inherited_start_api_key_marker
|
||||
|
||||
# Back-compat: --not-secure is a deprecated alias for --no-secure.
|
||||
secure = _resolve_secure(secure, not_secure)
|
||||
extra_llama_args: List[str] = list(ctx.args) if ctx.args else []
|
||||
|
|
@ -1991,23 +2004,28 @@ def run(
|
|||
args.append("--no-cloudflare")
|
||||
args.append("--secure" if secure else "--no-secure")
|
||||
args.append("--tensor-parallel" if tensor_parallel else "--no-tensor-parallel")
|
||||
if start_api_key_marker:
|
||||
args.append("--start-api-key-marker")
|
||||
if verbose:
|
||||
args.append("--verbose")
|
||||
# llama-server pass-through extras → child ctx.args → load payload.
|
||||
if extra_llama_args:
|
||||
args.extend(extra_llama_args)
|
||||
|
||||
if sys.platform == "win32":
|
||||
proc = subprocess.Popen(args)
|
||||
try:
|
||||
rc = proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
rc = proc.wait()
|
||||
raise typer.Exit(rc)
|
||||
else:
|
||||
os.execvp(str(studio_bin), args)
|
||||
if start_api_key_marker:
|
||||
os.environ[_START_API_KEY_MARKER_ENV] = "1"
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
proc = subprocess.Popen(args)
|
||||
try:
|
||||
rc = proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
rc = proc.wait()
|
||||
raise typer.Exit(rc)
|
||||
else:
|
||||
os.execvp(str(studio_bin), args)
|
||||
finally:
|
||||
# execvp does not return on success. Restore the parent environment
|
||||
# after Windows waits for the child, or if launch fails.
|
||||
os.environ.pop(_START_API_KEY_MARKER_ENV, None)
|
||||
|
||||
# ── 2. Start server (always suppress built-in banner) ─────────────
|
||||
run_mod = _load_run_module()
|
||||
|
|
|
|||
|
|
@ -170,13 +170,24 @@ def _install_reexec_capture(monkeypatch, *, platform):
|
|||
|
||||
monkeypatch.setattr(sys, "platform", platform)
|
||||
|
||||
def capture(kind, argv):
|
||||
captured.append(
|
||||
{
|
||||
"kind": kind,
|
||||
"argv": list(argv),
|
||||
"start_api_key_marker": studio_mod.os.environ.get(
|
||||
studio_mod._START_API_KEY_MARKER_ENV
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def fake_execvp(file, argv):
|
||||
captured.append({"kind": "execvp", "argv": list(argv)})
|
||||
capture("execvp", argv)
|
||||
raise _ExecCaptured(argv)
|
||||
|
||||
class _FakePopen:
|
||||
def __init__(self, argv, *a, **kw):
|
||||
captured.append({"kind": "popen", "argv": list(argv)})
|
||||
capture("popen", argv)
|
||||
self._argv = argv
|
||||
|
||||
def wait(self):
|
||||
|
|
@ -235,11 +246,28 @@ def test_reexec_forwards_parallel_all_aliases(monkeypatch, flag, value):
|
|||
), f"{flag} {value} was dropped on re-exec; argv = {argv}"
|
||||
|
||||
|
||||
def test_reexec_forwards_start_api_key_marker(monkeypatch):
|
||||
"""The internal progress marker must survive the studio-venv re-exec."""
|
||||
result, captured = _invoke_run(monkeypatch, _BASE + ["--start-api-key-marker"])
|
||||
@pytest.mark.parametrize("platform", ["linux", "darwin", "win32"])
|
||||
def test_reexec_hands_off_start_api_key_marker_out_of_band(monkeypatch, platform):
|
||||
"""A new child receives the marker while an old child sees no unknown flag."""
|
||||
result, captured = _invoke_run(
|
||||
monkeypatch,
|
||||
_BASE + ["--start-api-key-marker"],
|
||||
platform = platform,
|
||||
)
|
||||
assert len(captured) == 1, result.output
|
||||
assert "--start-api-key-marker" in captured[0]["argv"]
|
||||
assert "--start-api-key-marker" not in captured[0]["argv"]
|
||||
assert captured[0]["start_api_key_marker"] == "1"
|
||||
|
||||
|
||||
def test_reexeced_child_consumes_start_api_key_marker_env(monkeypatch):
|
||||
"""A supported child consumes the handoff before starting descendants."""
|
||||
studio_mod = _load_run_command()
|
||||
monkeypatch.setenv(studio_mod._START_API_KEY_MARKER_ENV, "1")
|
||||
|
||||
inherited = studio_mod._consume_start_api_key_marker_env()
|
||||
|
||||
assert inherited is True
|
||||
assert studio_mod._START_API_KEY_MARKER_ENV not in studio_mod.os.environ
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ["linux", "darwin", "win32"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue