How to implement a user inactivity timeout?

  1. Developing this app locally
  2. NA (not deployed)
  3. NA - local
  4. NA - no error messages
  5. Versions - streamlit 1.14.0, python 3.8.10

I searched the discussions and found a handful of similar requests.

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()

Hi @IndigoJay

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?
    ...

I want the application to run a home built logout() function after 60 minutes of user inactivity.

I think i see what you mean, st.stop() doesn’t really stop streamlit in this case:

import streamlit as st
import queue

st.title("testing queue timer")

q = queue.SimpleQueue()

if st.button("click me"):
    st.write(1+1)

try:
    q.get(block=True, timeout=10)
except queue.Empty:
    st.warning("10 second timeout. stopping streamlit ")
    st.stop() # doesn't actually stop streamlit

st.stop() will finish the execution of the script, that will happen anyway at that point. But you can call logout() instead, whatever it does…

I notice the Streamlit running message in the top right is constantly animated while the queue is going, is this a bad thing?

have u considered:

  1. Checking user timout logic in a separate thread: if the user times out: pushing True to queue.Queue()
  2. 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()
  3. wrapping ur logics inside @check_user_timeout

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.

I considered and even tried it and failed. I would love to see a imple example of that working.

1 Like

can u share me your codebase? I would like to explore and fix it

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!

Unfortunately I dont’t keep any of that.

@Goyo check this out: https://timeout.streamlit.app

There are so many things that used to work but no longer working in streamlit

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.