Server lifecycle hooks
Server applications may need to support code that runs outside of a session. The example we know of right now is a streaming IO observer, that would listen for new data from some source, and update all sessions when new data arrives.
A possible approach would be that in an application directory, we could have a file lifecycle.py
(lifecycle_hooks.py
? hooks.py
? server_events.py
?)
It would contain functions with conventional names (all of them are optional):
def on_application_load(application_context_object):
pass
def on_application_unload(application_context_object):
pass
def on_session_created(session_context_object):
pass
def on_session_destroyed(session_context_object):
pass
We would load and execute this file as a module one time, extract the callbacks by name, and then run them at appropriate times.
application_context_object
would have methods for adding/removing callbacks, probably should include periodic, timeout, and immediate (IOLoop.add_callback/spawn_callback).
session_context_object
would have a way to get the session document with the session lock held, maybe a context manager object and/or a method that takes a function that's run with the lock held. It would also have a way to get the session ID.
Implementation-wise, we could add methods to bokeh.application.handlers.Handler
and bokeh.application.Application
corresponding to each hook, and then the Server
would invoke those hooks as needed.
We could either never call these outside of a server (bokeh html
case), or we could make the bokeh html
case look like we are creating one application and one session... adding callbacks to application_context_object in the bokeh html
case would do nothing...
An issue is that an application-wide callback might want to access session documents. A way this could work is that you'd collect session context objects from on_session_created
into a global, and then in your application-wide callback you could iterate over them. Or we could simply have a sessions
property on the application context object that would return all live session context objects.
Some sample uses.
In on_application_load
, you would add an async callback which would run forever in a loop and update all sessions when there's a change:
@gen.coroutine
def listen_for_changes(app_context):
new_data = yield fetch_data_from_remote_source()
for session_context in app_context.sessions:
with (yield session_context.locked_document()) as doc:
doc.data['foo'].append(new_data)
def on_application_load(app_context):
app_context.add_callback(lambda: listen_for_changes(app_context))
An alternative way to handle document locking would be to add a callback to the document, since document callbacks are always invoked with the document lock:
def apply_changes(doc, new_data):
doc.data['foo'].append(new_data)
@gen.coroutine
def listen_for_changes(app_context):
new_data = yield fetch_data_from_remote_source()
for session_context in app_context.sessions:
session.document.add_callback(lambda: apply_changes(session.document, new_data))
# note that the hooks can be coroutines
@gen.coroutine
def on_session_created(session_context):
yield notify_my_load_balancer(session_context.id)
def on_session_created(session_context):
if not check_signature(session_context.id):
session_context.destroy() # we would have to have this "get rid of the session" API