parked relay: a supervisor that keeps it alive + a REMOTE restart the operator can actually reach
Completes the travelling-operator deployment. The relay stays parked on the
home machine (so no player ever edits join.bat) and the console dials in from
anywhere -- but until now two things needed hands on that machine: bringing the
relay back when it died, and restarting it (the only way to clear a wedge or
pick up a roster resize). Both are covered now.
tools/btrelay_park.py -- the supervisor:
* runs the relay with cwd=content\ , which is what pins WHICH
operator_secret.txt is live (the trap documented last commit);
* relaunches on ANY exit, with 2/5/15/30/60s backoff when a relay dies inside
20s, so a permanent fault (port taken, missing egg) cannot become a spin;
* rotates content\parked_relay.log to .1 first, so the dead generation's
evidence survives the relaunch that replaces it;
* Ctrl-C stops it for good. NOT a Windows service on purpose: session 0
would hide the window an operator wants to tail.
tools/park_relay.cmd -- double-click to park; a shortcut to it in shell:startup
gives start-after-reboot with no admin rights.
`restart` on the control port (btconsole.py): sets restart_requested, the run
loop returns, relay_main exits RESTART_EXIT_CODE=42 and the supervisor relaunches
-- re-reading the egg. REFUSED while a mission is running (launches_sent >= 2
and not stop_sent): bouncing then would drop every pod out of a live round.
Local stdin gets the same command for parity.
btoperator.py: Restart Session in REMOTE mode now sends `restart` and reconnects
6s later instead of just tearing down our own link -- the old behaviour looked
like "Restart does nothing" against a parked relay. (QTimer had to be imported;
it was missing, which py_compile does not catch -- it would have been a runtime
NameError on the first click.)
VERIFIED by scratchpad/test_parked_supervisor.py, 12/12, and the full
test_remote_console_e2e.py re-run green afterwards (15/15):
* kill the relay -> a NEW pid is serving again, and the old log is kept as .1;
* remote `restart` -> ACKed, new pid, egg re-read, serving again;
* the supervisor distinguishes the two -- "operator requested a restart" vs
the crash/backoff path -- proven from its own log, not assumed;
* the mid-mission guard is present and wired to launches_sent/stop_sent.
Two harness defects fixed while proving it, both mine: a check written with
`or True` that could never fail (replaced with two real assertions against the
supervisor's log), and a recv that treated the relay's correct
close-after-restart as a ConnectionResetError failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
05f1ffb194
commit
05fdd319d6
+17
-1
@@ -24,7 +24,8 @@ import socket
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from PySide6.QtCore import Qt, QProcess, QProcessEnvironment, QThread, Signal
|
||||
from PySide6.QtCore import (Qt, QProcess, QProcessEnvironment, QThread, QTimer,
|
||||
Signal)
|
||||
from PySide6.QtGui import QColor, QFont, QPalette, QAction
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QCheckBox, QComboBox, QFileDialog, QFormLayout, QGroupBox,
|
||||
@@ -966,6 +967,21 @@ class Operator(QMainWindow):
|
||||
def _restart_session(self):
|
||||
"""Next round: same mission/roster, fresh session. Players just
|
||||
re-run their join script."""
|
||||
if getattr(self, "remote_link", None):
|
||||
# PARKED RELAY: we do not own that process, so tearing down our
|
||||
# own link would only disconnect us (the old behaviour, which
|
||||
# looked like "Restart did nothing"). Ask the relay itself to
|
||||
# restart -- its supervisor relaunches it, re-reading the egg, so
|
||||
# a roster resize or a wedge is recoverable from the road. Then
|
||||
# reconnect once it is back up.
|
||||
host = self.f_relayhost.text().strip()
|
||||
self.log.appendPlainText(
|
||||
"== asking the parked relay at %s to RESTART "
|
||||
"(reconnecting in a few seconds) ==" % host)
|
||||
self._relay_send("restart")
|
||||
self._stop_session()
|
||||
QTimer.singleShot(6000, self._start_session)
|
||||
return
|
||||
self.log.appendPlainText("== restarting session ==")
|
||||
self._stop_session()
|
||||
self._start_session()
|
||||
|
||||
Reference in New Issue
Block a user