-
Notifications
You must be signed in to change notification settings - Fork 36
Implement Auto-Shutdown When Browser Closes (solves Issue #151) #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| LOCAL_RUN=true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,116 @@ | ||
| import streamlit as st | ||
| import streamlit.components.v1 as components | ||
| from pathlib import Path | ||
| import json | ||
| # For some reason the windows version only works if this is imported here | ||
| import pyopenms | ||
| import os | ||
| import time | ||
| import threading | ||
| import signal | ||
| import tornado.web | ||
| import tornado.ioloop | ||
| import pyopenms # required import for Windows | ||
|
|
||
| # Keep settings at the very top | ||
| if "settings" not in st.session_state: | ||
| with open("settings.json", "r") as f: | ||
| st.session_state.settings = json.load(f) | ||
| with open("settings.json", "r") as f: | ||
| st.session_state.settings = json.load(f) | ||
|
|
||
| # --- Global Session Counter --- | ||
| global_active_sessions = 0 | ||
| last_heartbeat = 0 # Global heartbeat timestamp | ||
| lock = threading.Lock() # Protects modifications to globals | ||
|
|
||
| # Add a flag to ensure Tornado starts only once. | ||
| if "tornado_server_started" not in globals(): | ||
| globals()["tornado_server_started"] = False | ||
|
|
||
|
|
||
| def increment_active_sessions(): | ||
| global global_active_sessions | ||
| with lock: | ||
| global_active_sessions += 1 | ||
| print(f"Session connected, total active sessions: {global_active_sessions}") | ||
|
|
||
| def decrement_active_sessions(): | ||
| global global_active_sessions | ||
| with lock: | ||
| global_active_sessions -= 1 | ||
| print(f"Session disconnected, total active sessions: {global_active_sessions}") | ||
| if global_active_sessions <= 0: | ||
| shutdown() | ||
|
|
||
| def shutdown(): | ||
| print("🚨 No active users. Shutting down Streamlit server...") | ||
| os.kill(os.getpid(), signal.SIGTERM) | ||
|
|
||
| # Register this session only once. | ||
| if "registered" not in st.session_state: | ||
| st.session_state.registered = True | ||
| increment_active_sessions() | ||
|
|
||
| # --- Tornado Endpoints --- | ||
|
|
||
| # This handler is called on browser unload (if it fires) | ||
| class CloseAppHandler(tornado.web.RequestHandler): | ||
| def post(self): | ||
| time.sleep(1) | ||
| decrement_active_sessions() | ||
| self.write("OK") | ||
|
|
||
|
Comment on lines
+53
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add client-side usage of the <script>
function sendHeartbeat() {
fetch('http://localhost:8502/heartbeat', {method: 'POST'});
}
// ...
+ window.addEventListener('beforeunload', () => {
+ navigator.sendBeacon('http://localhost:8502/_closeapp');
+ });
</script>Also applies to: 70-71 |
||
| # This handler receives heartbeat pings | ||
| class HeartbeatHandler(tornado.web.RequestHandler): | ||
| def post(self): | ||
| global last_heartbeat | ||
| with lock: | ||
| last_heartbeat = time.time() | ||
| self.write("OK") | ||
|
|
||
| def start_tornado_server(): | ||
| routes = [ | ||
| (r"/_closeapp", CloseAppHandler), | ||
| (r"/heartbeat", HeartbeatHandler) | ||
| ] | ||
| app = tornado.web.Application(routes) | ||
| app.listen(port=8502) # Adjust if needed; matches Streamlit port. | ||
| tornado.ioloop.IOLoop.current().start() | ||
|
|
||
| threading.Thread(target=start_tornado_server, daemon=True).start() | ||
|
|
||
| # Monitor for lost heartbeat and shutdown if no activity. | ||
| def heartbeat_monitor(): | ||
| global last_heartbeat | ||
| # Initialize heartbeat time | ||
| with lock: | ||
| last_heartbeat = time.time() | ||
| while True: | ||
| time.sleep(5) | ||
| # If no heartbeat received in 15 seconds and no sessions are active, shutdown | ||
| current_time = time.time() | ||
| with lock: | ||
| elapsed = current_time - last_heartbeat | ||
| active = global_active_sessions | ||
| if elapsed > 15: | ||
| shutdown() | ||
|
|
||
| threading.Thread(target=heartbeat_monitor, daemon=True).start() | ||
|
|
||
| # --- Client-side JavaScript injections --- | ||
| def insert_heartbeat_script(): | ||
| # Sends a heartbeat every 3 seconds. | ||
| components.html( | ||
| """ | ||
| <script> | ||
| function sendHeartbeat() { | ||
| fetch('http://localhost:8502/heartbeat', {method: 'POST'}); | ||
| } | ||
| // Send an immediate heartbeat on load | ||
| sendHeartbeat(); | ||
| // Then every 3 seconds | ||
| setInterval(sendHeartbeat, 3000); | ||
| </script> | ||
| """, | ||
| height=0, | ||
| ) | ||
Ayushmaan06 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if __name__ == '__main__': | ||
| pages = { | ||
|
|
@@ -33,4 +137,8 @@ | |
| } | ||
|
|
||
| pg = st.navigation(pages) | ||
| pg.run() | ||
| pg.run() | ||
|
|
||
| if os.getenv("LOCAL_RUN", "true").lower() == "true": | ||
| # Inject the heartbeat script first so the server sees regular pings. | ||
| insert_heartbeat_script() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,4 +8,5 @@ plotly==5.22.0 | |
| captcha==0.5.0 | ||
| pyopenms_viz==1.0.0 | ||
| streamlit-js-eval | ||
| psutil==7.0.0 | ||
| tornado>=6.2.0 | ||
| psutil==7.0.0 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to import? https://github.com/OpenMS/streamlit-template/blob/main/requirements.txt