I have developed an Streamlit app that is nearly ready to go into live internally. However my organization’s security policy requires production applications have a 60 minute user inactivity timeout.
I’ve tried and failed with approaches such as abusing st.cache(), st.experimental_rerun(), time.sleep() and threading.
Below is a my final not-working attempt before throwing in the towel.
This code is by no means a good way to go or anything I am locked into! Maybe there is a Python or Streamlit way?
import streamlit as st
import time
import threading
TIMEOUT_MINUTES = 1 # one minute for testing :)
def check_inactivity(timeout_minutes=TIMEOUT_MINUTES):
while True:
last_activity_timestamp = st.session_state.last_activity_timestamp
st.write(f"Checking inactivity at {last_activity_timestamp}")
current_time = time.time()
time_difference = current_time - last_activity_timestamp
if time_difference > timeout_minutes * 60:
st.warning("Session timed out due to inactivity.")
st.stop()
time.sleep(30)
def update_last_activity_timestamp():
st.session_state.last_activity_timestamp = time.time()
def main():
st.set_page_config(page_title="Test Inactivity Timeout", page_icon=":rocket:")
if "last_activity_timestamp" not in st.session_state:
st.session_state.last_activity_timestamp = time.time()
inactivity_checker = threading.Thread(target=check_inactivity)
inactivity_checker.daemon = True
inactivity_checker.start()
st.header("Test inactivity timeout")
st.button("test", on_click=update_last_activity_timestamp)
if __name__ == "__main__":
main()
There are previous related post as follows tackling the same issue of user inactivity timeout:
Another approach could be to reset important session state variables of the app after a certain amount of time has elapsed if there were no changes made to those session state variables.
Using thread timers is leading me into a part of Streamlit with errors like
missing ScriptRunContext
and github issues with warnings like:
[Jan 28, 2022]
… this is still an internal Streamlit API and therefore unsupported and subject to change. (We don’t try to break peoples’ code when we change internals, but we don’t make any guarantees that we won’t.)
Note also that issuing Streamlit commands from other threads is undefined behavior. Streamlit’s various st.foo commands are not thread safe, so please don’t do this in production code as you’ll be introducing race conditions into your apps!
Before dealing with threading issues, what is supposed to happen after the timeout? Because, as far as I know, there is no API to kill a session. You are calling st.stop(), but you have that for free as long as your app is free of infinite loops.
That said, I think I found a way to detect a timeout that seems to work. But I still don’t know what to do with it.
import queue
q = queue.SimpleQueue()
# Your code here.
try:
q.get(block=True, timeout=5)
except queue.Empty:
# What should hapen here?
...
Checking user timout logic in a separate thread: if the user times out: pushing True to queue.Queue()
Define a parent decorator function (lets call it- “check_user_timeout”): that checks if queue is empty or not : and if its non-empty: then calls st.stop()
Yes, the same happens during a call to time.sleep(). Indeed all the waiting and listening from events is just an abstraction, there is always some active polling under the hood. It is the price you have to pay for synchrinizing threads or processes and it is supposed to be cheap. If in doubt, measure.
An approach from a different direction. Resetting a timestamp at the end of the streamlit code, and checking if the current time and the timestamp are greater in difference than x minutes. This doesn’t really shut down the program at x minutes, but if the user tries to interact with the program after x minutes, then that interaction will trigger the timeout.
Edit: I need to think about the implications of this approach. What if the user waits a few hours then clicks a button to write data to a database, Would this action happen, would it be well past the timeout. or would the time out trigger.
In this example, i start the program, wait 30+ seconds, then click the button, the value of the button is not printed to the page.
import streamlit as st
import time
if "last_activity_timestamp" not in st.session_state:
st.session_state.last_activity_timestamp = time.time()
st.title("Testing timer")
st.write(f"Last Activity Time: {st.session_state.last_activity_timestamp}")
time_difference = time.time() - st.session_state.last_activity_timestamp
st.write(f"Time Difference: {time_difference}")
if time_difference > 0.5 * 60: ## 30 seconds because testing
st.warning("Session timed out due to inactivity.")
st.stop()
if st.button("click me"):
st.write(1+1)
st.session_state.last_activity_timestamp = time.time() # This must be at the end!
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.