aboutsummaryrefslogtreecommitdiff
path: root/app/ui
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui')
-rw-r--r--app/ui/runview.py47
1 files changed, 39 insertions, 8 deletions
diff --git a/app/ui/runview.py b/app/ui/runview.py
index d65978d..f016922 100644
--- a/app/ui/runview.py
+++ b/app/ui/runview.py
@@ -44,6 +44,7 @@ _SERVER_STATES = {
"starting": ("starting", "warn"),
"ready": ("ready", "ok"),
"processing": ("processing", "ok"),
+ "stopping": ("stopping", "warn"),
"down": ("not responding", "err"),
"error": ("error", "err"),
"stopped": ("stopped", "info"),
@@ -113,6 +114,7 @@ class RunView:
self.finished_at: Optional[float] = None
self.boot_started: Optional[float] = None
self.convert_started: Optional[float] = None
+ self.stop_started: Optional[float] = None
self.server_log_path = ""
# -- threads ---------------------------------------------------
self._queue: Queue = Queue()
@@ -156,6 +158,8 @@ class RunView:
elif kind == "server_down":
if self.phase == "convert":
self.server = "down"
+ elif kind == "server_stopped":
+ self.server = "stopped"
elif kind == "book":
self.phase = "convert"
self.book = (event.get("index"), event.get("total"),
@@ -349,7 +353,13 @@ class RunView:
return True
def _confirm_stop_server(self) -> None:
- """Ask whether to stop the server this run started (once)."""
+ """Ask whether to stop the server this run started (once).
+
+ On "yes" the stop runs on a background thread while the screen keeps
+ redrawing the server panel — showing "stopping" with an elapsed clock,
+ mirroring the boot screen — so the SIGTERM grace period never freezes
+ the TUI. Returns once the server is gone.
+ """
if not self.started_server or self._server_stopped_confirmed:
return
self._server_stopped_confirmed = True
@@ -357,13 +367,32 @@ class RunView:
if not name or not servers.alive(name):
return
self._blocking()
- answer = tui.confirm(self.scr,
- f"Stop the '{name}' server now?", default=True,
- cancel_value=False)
- if answer:
- with contextlib.redirect_stdout(io.StringIO()):
- servers.stop(name)
- self.server = "stopped"
+ try:
+ answer = tui.confirm(self.scr,
+ f"Stop the '{name}' server now?", default=True,
+ cancel_value=False)
+ finally:
+ self._nonblocking()
+ if not answer:
+ return
+ self.server = "stopping"
+ self.stop_started = self._now()
+ done = threading.Event()
+
+ def _stop() -> None:
+ try:
+ with contextlib.redirect_stdout(io.StringIO()):
+ servers.stop(name)
+ finally:
+ self._queue.put({"kind": "server_stopped"})
+ done.set()
+
+ threading.Thread(target=_stop, daemon=True).start()
+ while not done.wait(_DRAW_TIMEOUT_MS / 1000.0):
+ self._drain()
+ self.render()
+ self._drain()
+ self.render()
def _blocking(self) -> None:
"""Make getch block (used while a confirm dialog owns the screen)."""
@@ -417,6 +446,8 @@ class RunView:
self.server, (self.server, "info"))
if self.server == "starting" and self.boot_started is not None:
state_text += f" ({int(self._now() - self.boot_started)}s)"
+ elif self.server == "stopping" and self.stop_started is not None:
+ state_text += f" ({int(self._now() - self.stop_started)}s)"
if self.config.autostart_spec is None and self.server == "ready":
state_text += " (external)"
_text(scr, theme, y, inner_x, "Status".ljust(label_w), theme["dim"])