fix(gui): make Quit button actually terminate the server
Signalling the process with SIGTERM/SIGINT didn't reliably shut Streamlit
down — its tornado/asyncio loop swallowed or deferred the signal, so the
browser saw the websocket drop ("Connection error") while the python
process kept running. Replace the signal with a daemon-thread
``os._exit(0)`` after a short delay so the current rerun can paint the
"shutting down" message before the process is hard-killed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
@@ -90,21 +91,27 @@ def hide_streamlit_chrome() -> None:
|
||||
def quit_button(label: str = "Quit app", *, key: str = "quit_app_button") -> None:
|
||||
"""Render a Quit button that terminates the Streamlit server.
|
||||
|
||||
Streamlit has no first-class shutdown hook, so closing the browser tab
|
||||
leaves the server (and the python process running it) alive — the user
|
||||
has to Ctrl+C in the shell. This helper signals the Streamlit process
|
||||
so the shell returns cleanly. SIGTERM lets Streamlit run its own
|
||||
shutdown handlers; if that's unavailable on the platform, fall back to
|
||||
SIGINT (the same signal Ctrl+C delivers).
|
||||
Streamlit has no first-class shutdown hook, and signalling the
|
||||
process (SIGTERM/SIGINT) does not reliably terminate it — Streamlit
|
||||
installs its own handlers and the tornado/asyncio loop swallows or
|
||||
defers the signal, so the browser sees the websocket drop while the
|
||||
python process stays alive. To shut down cleanly we schedule
|
||||
``os._exit(0)`` on a daemon thread after a short delay; the delay
|
||||
lets the current rerun finish painting the "shutting down" message
|
||||
before the process is hard-killed.
|
||||
"""
|
||||
if st.session_state.get("_app_shutting_down"):
|
||||
st.success("App shut down. You can close this browser tab.")
|
||||
st.success("Shutting down… you can close this browser tab.")
|
||||
st.stop()
|
||||
|
||||
if st.button(label, key=key, type="secondary"):
|
||||
st.session_state["_app_shutting_down"] = True
|
||||
sig = getattr(signal, "SIGTERM", signal.SIGINT)
|
||||
os.kill(os.getpid(), sig)
|
||||
|
||||
def _hard_exit() -> None:
|
||||
time.sleep(0.5)
|
||||
os._exit(0)
|
||||
|
||||
threading.Thread(target=_hard_exit, daemon=True).start()
|
||||
st.rerun()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user