How to make server aware that the browser is gone?

Greetings,

I know this is an FAQ, yet could not find any simple answer.

The trouble is that an end-user may close the browser or tab where the Bokeh server has opened a document. The server keeps on happily jogging, although the GUI is gone.

So, is there a way the server could probe the browser to see if the document is still viewed? Based on what is said in some discussions, the browser does not create any events when the browser or tab is closed, but I suppose even a ping type query would suffice.

Cheers,
heke

1 Like

Hi @heke

The bokeh server keeps track of sessions automatically. There are two options in particular exposed to control this behavior. The --check-unused-sessions option allows one to specify how frequently the server checks for sessions that are no longer used and the --unused-session-lifetime specifies how long a session flagged as unused persists before being destroyed.

You can see how to configure these options in the bokeh reference document here. See the subsection labeled Session Expiration Options.

Regarding …

this implies that the behavior – which is expected so the server can handle sessions from other clients – is problematic in your use case. Is that so?

If you need to be able to do something specific when a session is no longer used, you can leverage bokeh server’s lifecycle hooks, which are described in the documentation here.

Specifically, you could implement any custom logic required for your specific use-case in the on_session_destroyed callback, which is invoked when an unused session, described above, is actually removed by the server machinery.

Hope this helps.

3 Likes

Hi _jm,

Thank you for your very helpful advice.
I briefly studied the lifecycle hooks earlier, but wandered away due to hesitation.
Will try them out next.

Indeed the case is that need to do a graceful exit, that is, disable some connected hardware and close the connection, when user closes the browser.

100 * (“thanks”)
heke

Greetings _jm, all,

I could make it work using the suggested method. Many thanks. For anyone having similar issue, a minimum code is shown below. Will exit the application after timeouts when the browser tab is closed. The timeouts are settable at server initialization.

However, I added a line of code to the example, which is causing some amazement. It may be due to some race condition: If having a periodic callback and the callback contains a blocking delay, the first time the callback is called, the document initialization is ran again. In my machine, if the callback period is set less than 400ms, the anomaly happens (if one runs the example code below, can perhaps see that app_init() gets called twice).

The way to avoid this is to ensure that for the first second after the document is initialized, one should not have blocking calls, I guess.

# -*- coding: utf-8 -*-
"""
Minimum for session destroy anomaly case.
Having long delay in periodic callback causes a call to cleanup_session()
which in turn runs app_init() again, but only once.
This happens only if periodic_callback is set to less than ~400ms
"""
from time import sleep
from tornado.ioloop import IOLoop
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
from bokeh.server.server import Server

def my_periodic_cb():
    print("my_periodic_cb()")
    sleep(3)

def my_cleanup_session_cb(session_context):
    print("my_cleanup_session_cb()")
    print("Destroyed =", session_context.destroyed)

def app_init(doc):
    print("app_init()")
    print("Destroyed =", doc.session_context.destroyed)
    doc.on_session_destroyed(my_cleanup_session_cb)
    doc.add_periodic_callback(my_periodic_cb, 100)  
    return doc
      
io_loop = IOLoop.current()
bokeh_app = Application(FunctionHandler(app_init))
server = Server({"/": bokeh_app}, io_loop=io_loop,
                 check_unused_sessions_milliseconds=1000,
                 unused_session_lifetime_milliseconds=1000)

if server._started == False:
    server.start()

io_loop.add_callback(server.show, "/")  
try:
    io_loop.start()
except:
    pass
  

Cheers,
Heke

2 Likes