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:
Leonid Pershin
2026-08-21 20:12:03 +03:00
parent a882964ce0
commit f8f8dcc93e
14 changed files with 81 additions and 79 deletions
+51 -47
View File
@@ -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)