Update documentation to clarify GPU control commands
- Revised README, architecture, CLI, and other documentation to specify that both `Ctrl+C` and `Ctrl+D` now stop the GPU while preserving disk data, correcting previous inaccuracies. - Enhanced access card and setup instructions to reflect the updated command behavior for better user understanding. - Updated tests to ensure the new command behaviors are validated and documented correctly, improving overall clarity in GPU management.
This commit is contained in:
@@ -125,8 +125,7 @@ def render_access_panel(
|
||||
cmds.add_row("hold killer", "gpu-rent hold")
|
||||
cmds.add_row("стоп GPU", "gpu-rent stop")
|
||||
if tunneled:
|
||||
cmds.add_row("Ctrl+C", "туннель off, GPU жив")
|
||||
cmds.add_row("Ctrl+D", "stop GPU, диски остаются")
|
||||
cmds.add_row("Ctrl+C / Ctrl+D", "stop GPU, диски остаются")
|
||||
if not tunneled:
|
||||
cmds.add_row("туннель", "gpu-rent tunnel --open")
|
||||
|
||||
@@ -220,7 +219,7 @@ def print_access_card(
|
||||
for line in mcp_snippet_lines(cfg):
|
||||
log(line)
|
||||
log("hold: gpu-rent hold | stop: gpu-rent stop")
|
||||
log("Ctrl+C — туннель off, GPU жив | Ctrl+D — stop GPU")
|
||||
log("Ctrl+C / Ctrl+D — stop GPU (диски остаются)")
|
||||
log("")
|
||||
return
|
||||
from gpu_rent.term import console as default_console
|
||||
|
||||
+3
-3
@@ -49,7 +49,7 @@ def _die(exc: BaseException) -> None:
|
||||
err(str(exc))
|
||||
hint = (
|
||||
"Дальше: gpu-rent status · gpu-rent logs · gpu-rent stop "
|
||||
"(Ctrl+C на туннеле GPU не гасит; Ctrl+D гасит)"
|
||||
"(Ctrl+C / Ctrl+D на туннеле гасят GPU, диски остаются)"
|
||||
)
|
||||
msg = str(exc)
|
||||
if "gpu-rent status" not in msg and "Дальше:" not in msg:
|
||||
@@ -809,7 +809,7 @@ def logs(
|
||||
def tunnel(
|
||||
open_browser: bool = typer.Option(False, "--open", help="Открыть браузер на 17801"),
|
||||
) -> None:
|
||||
"""SSH localhost:17801 -> VM :7801. Ctrl+C — туннель off; Ctrl+D — stop GPU."""
|
||||
"""SSH localhost:17801 -> VM :7801. Ctrl+C / Ctrl+D — stop GPU (диски остаются)."""
|
||||
try:
|
||||
cfg = load_config(require_auth=True)
|
||||
state = load_state()
|
||||
@@ -1052,7 +1052,7 @@ def resize_data(gb: int = typer.Option(..., "--gb", help="Новый разме
|
||||
|
||||
watchdog_app = typer.Typer(
|
||||
help=(
|
||||
"Локальный сервис: если туннель умер без Ctrl+C / Ctrl+D / stop — "
|
||||
"Локальный сервис: если процесс туннеля умер без stop (kill/reboot) — "
|
||||
"через grace удалить compute. Не путать с idle-killer на VM."
|
||||
),
|
||||
no_args_is_help=True,
|
||||
|
||||
@@ -423,7 +423,7 @@ def dry_run_plan(checks: list[Check]) -> list[str]:
|
||||
"₽: в API нет — смотри панель; диск 24/7 даже после stop",
|
||||
f"туннель: localhost:{cfg.swarmui_local_port} -> VM :7801",
|
||||
"gpu-rent up --yes создаст GPU + SwarmUI и откроет туннель :17801 "
|
||||
"(Ctrl+C не гасит GPU, Ctrl+D гасит)",
|
||||
"(Ctrl+C / Ctrl+D гасят GPU)",
|
||||
"только облако без туннеля: gpu-rent up --yes --no-tunnel",
|
||||
]
|
||||
flavor = next((c.detail for c in checks if c.name == "flavor" and c.ok), None)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
VM idle-killer remains the default safety net. This module is opt-in via
|
||||
`gpu-rent watchdog install`: while a tunnel is armed, a scheduled tick stops
|
||||
compute if the laptop/process died without Ctrl+C detach or `gpu-rent stop`.
|
||||
compute if the laptop/process died without Ctrl+C/Ctrl+D ``stop`` or `gpu-rent stop`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -189,7 +189,7 @@ def touch_heartbeat() -> None:
|
||||
|
||||
|
||||
def detach_lease_keep_gpu() -> None:
|
||||
"""Ctrl+C on tunnel: leave GPU running; local tick must not stop."""
|
||||
"""Mark the lease detached so the local tick will not stop compute."""
|
||||
lease = load_lease()
|
||||
if lease is None:
|
||||
lease = LocalLease()
|
||||
@@ -271,7 +271,7 @@ def install_watchdog(
|
||||
log(
|
||||
f"local-watchdog установлен ({platform}): тик каждые {interval} мин. "
|
||||
f"Grace {grace_seconds() // 60} мин после смерти процесса туннеля → stop. "
|
||||
f"Ctrl+C на туннеле GPU не гасит; Ctrl+D — stop. "
|
||||
f"Ctrl+C / Ctrl+D на туннеле — stop. "
|
||||
"При SELECTEL_API_TOKEN — toast каждые BALANCE_NOTIFY_STEP_RUB ₽ (дефолт 200)."
|
||||
)
|
||||
return marker
|
||||
|
||||
@@ -99,7 +99,7 @@ def run_setup(
|
||||
|
||||
do_wd = install_watchdog
|
||||
if do_wd is None and confirm:
|
||||
do_wd = confirm("Установить local-watchdog (аварийный stop без Ctrl+C)?")
|
||||
do_wd = confirm("Установить local-watchdog (аварийный stop, если процесс умер)?")
|
||||
if do_wd:
|
||||
from gpu_rent.local_watchdog import install_watchdog as _install
|
||||
|
||||
|
||||
+51
-47
@@ -1,6 +1,6 @@
|
||||
"""SSH local forward with Nova watchdog.
|
||||
|
||||
Ctrl+C closes the tunnel and leaves the GPU. Ctrl+D (EOF) runs ``stop``.
|
||||
Ctrl+C and Ctrl+D (EOF) both run ``stop`` (disks kept).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -242,7 +242,7 @@ def run_tunnel(
|
||||
current_host = host
|
||||
for loc, rem in forwards:
|
||||
log(f"туннель 127.0.0.1:{loc} -> {current_host}:{rem}")
|
||||
log("Ctrl+C — туннель off, GPU жив. Ctrl+D — stop GPU (диски остаются).")
|
||||
log("Ctrl+C / Ctrl+D — stop GPU (диски остаются).")
|
||||
log("watchdog: EXPIRED → unshelve + reconnect")
|
||||
|
||||
server = _start_forwarder(cfg, current_host, forwards)
|
||||
@@ -255,46 +255,61 @@ def run_tunnel(
|
||||
else:
|
||||
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||
|
||||
from gpu_rent.ready import verify_stack_local
|
||||
|
||||
try:
|
||||
local_checks = verify_stack_local(cfg, log, timeout=90.0)
|
||||
state = load_state()
|
||||
state.notes = dict(state.notes or {})
|
||||
state.notes["stack_local"] = [
|
||||
{"name": c.name, "ok": c.ok, "detail": c.detail} for c in local_checks
|
||||
]
|
||||
save_state(state)
|
||||
except CloudError as exc:
|
||||
log(f"проверка туннеля: {exc}")
|
||||
raise
|
||||
|
||||
from gpu_rent.access_card import print_access_card
|
||||
|
||||
print_access_card(cfg, tunneled=True, host=current_host)
|
||||
|
||||
if open_browser:
|
||||
webbrowser.open(open_url)
|
||||
|
||||
state = load_state()
|
||||
state.phase = "ready_tunneled"
|
||||
save_state(state)
|
||||
|
||||
from gpu_rent.local_watchdog import (
|
||||
detach_lease_keep_gpu,
|
||||
clear_lease,
|
||||
start_heartbeat_thread,
|
||||
stop_heartbeat_thread,
|
||||
watchdog_installed,
|
||||
)
|
||||
|
||||
if watchdog_installed():
|
||||
start_heartbeat_thread()
|
||||
log(
|
||||
"local-watchdog: heartbeat активен — аварийное закрытие "
|
||||
"(не Ctrl+C / не Ctrl+D) → stop после grace"
|
||||
)
|
||||
def _halt_gpu(reason: str) -> None:
|
||||
nonlocal server
|
||||
log(f"{reason} — гашу GPU (диски остаются)")
|
||||
_stop_forwarder(server)
|
||||
server = None
|
||||
stop_heartbeat_thread()
|
||||
clear_lease()
|
||||
if stop_gpu is not None:
|
||||
stop_gpu()
|
||||
else:
|
||||
from gpu_rent.session import cmd_stop
|
||||
|
||||
cmd_stop(cfg, log=log)
|
||||
log("туннель закрыт. GPU остановлен.")
|
||||
|
||||
try:
|
||||
if watchdog_installed():
|
||||
start_heartbeat_thread()
|
||||
log(
|
||||
"local-watchdog: heartbeat активен — kill/reboot без stop "
|
||||
"→ stop после grace"
|
||||
)
|
||||
|
||||
from gpu_rent.ready import verify_stack_local
|
||||
|
||||
try:
|
||||
local_checks = verify_stack_local(cfg, log, timeout=90.0)
|
||||
state = load_state()
|
||||
state.notes = dict(state.notes or {})
|
||||
state.notes["stack_local"] = [
|
||||
{"name": c.name, "ok": c.ok, "detail": c.detail} for c in local_checks
|
||||
]
|
||||
save_state(state)
|
||||
except CloudError as exc:
|
||||
log(f"проверка туннеля: {exc}")
|
||||
raise
|
||||
|
||||
from gpu_rent.access_card import print_access_card
|
||||
|
||||
print_access_card(cfg, tunneled=True, host=current_host)
|
||||
|
||||
if open_browser:
|
||||
webbrowser.open(open_url)
|
||||
|
||||
state = load_state()
|
||||
state.phase = "ready_tunneled"
|
||||
save_state(state)
|
||||
|
||||
if wait is not None:
|
||||
wait()
|
||||
return
|
||||
@@ -303,17 +318,7 @@ def run_tunnel(
|
||||
next_poll = time.time() + poll_seconds
|
||||
while True:
|
||||
if end_poll(1.0):
|
||||
log("Ctrl+D — гашу GPU (диски остаются)")
|
||||
_stop_forwarder(server)
|
||||
server = None
|
||||
stop_heartbeat_thread()
|
||||
if stop_gpu is not None:
|
||||
stop_gpu()
|
||||
else:
|
||||
from gpu_rent.session import cmd_stop
|
||||
|
||||
cmd_stop(cfg, log=log)
|
||||
log("туннель закрыт. GPU остановлен.")
|
||||
_halt_gpu("Ctrl+D")
|
||||
return
|
||||
if not server.is_active:
|
||||
next_poll = 0
|
||||
@@ -350,8 +355,7 @@ def run_tunnel(
|
||||
log(f"unshelve/reconnect fail: {exc}")
|
||||
return
|
||||
except KeyboardInterrupt:
|
||||
detach_lease_keep_gpu()
|
||||
log("Ctrl+C — туннель закрыт. GPU жив.")
|
||||
_halt_gpu("Ctrl+C")
|
||||
finally:
|
||||
stop_heartbeat_thread()
|
||||
_stop_forwarder(server)
|
||||
|
||||
+3
-3
@@ -49,9 +49,9 @@ def cost_and_risk_lines(cfg: Config, *, spot: bool, flavor_name: str) -> list[st
|
||||
f"{cfg.idle_minutes} мин пустой очереди → delete compute. Отложить: gpu-rent hold",
|
||||
"preemptible: хостер может усыпить (~24 ч окно) → EXPIRED; tunnel сам unshelve, "
|
||||
"или gpu-rent up",
|
||||
"Ctrl+C на tunnel — туннель off, GPU жив. Ctrl+D — stop GPU (диски остаются). "
|
||||
"Опционально: gpu-rent watchdog install — аварийное закрытие окна/ребут "
|
||||
"после grace тоже stop (Ctrl+C по-прежнему detach)",
|
||||
"Ctrl+C / Ctrl+D на tunnel — stop GPU (диски остаются). "
|
||||
"Опционально: gpu-rent watchdog install — kill/reboot окна "
|
||||
"после grace тоже stop",
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user