Hi all,
I’m attempting to use the bokeh server to present a bokeh application with the following workflow:
-
display a graph,
-
display controls,
-
when the user updates a control, trigger an event in the application:
a) run a query and do some analytics to build a new graph,
b) push the new graph to the client through the bokeh server.
The application works great for the most part, but if I change multiple controls too quickly (i.e. if I change a control in the client while the application is still building the graph for an earlier control change) then the application and client get into an infinite loop where they send PATCH-DOC messages back and forth, writing and overwriting the contents of the graph each time. I’ve written a small test case:
import time
import numpy
import datetime
import bokeh.client
import bokeh.plotting
import bokeh.models.widgets
n = 3
m = 5
numpy.random.seed(1)
data = numpy.cumsum(numpy.random.randn(n, m), axis = 1)
plot = bokeh.plotting.Figure()
plotWrapper = bokeh.models.widgets.HBox(plot)
buttons = bokeh.models.widgets.CheckboxButtonGroup(labels = map(str, range(n)), active = [0])
class Updater(object):
def __init__(self):
self.__count = 0
def __call__(self, *args, **kwargs):
print datetime.datetime.now(), "Starting update", self.__count, args, kwargs
plot = bokeh.plotting.Figure()
for index in buttons.active:
print datetime.datetime.now(), "Adding", index
plot.line(range(m), data[index])
print datetime.datetime.now(), "Sleeping"
time.sleep(5)
plotWrapper.children = [plot]
print datetime.datetime.now(), "Done with update", self.__count
self.__count += 1
update = Updater()
update()
buttons.on_click(update)
controls = bokeh.models.widgets.VBoxForm(buttons)
app = bokeh.models.widgets.VBox(controls, plotWrapper)
doc = bokeh.plotting.curdoc()
doc.add_root(app)
session = bokeh.client.push_session(doc)
print session.id
session.loop_until_closed()
``
If you start the bokeh server and run that script on the same machine, and then open a tab to http://localhost:5006/?bokeh-session-id= with the session ID printed by the application, you will see three checkbox buttons (with one active), and a graph with one line. If you quickly click the other two buttons, five seconds later a second line will show up, and then five seconds after that the client will start experiencing problems: it may temporarily show all three lines, and then flash back and forth between the two lines and the three lines, or it may just hang, taking up a full core.
The application will write output similar to the following, and will continue to use significant cpu:
2016-01-21 20:24:07.586236 Starting update 0 () {}
2016-01-21 20:24:07.590865 Adding 0
2016-01-21 20:24:07.593379 Sleeping
2016-01-21 20:24:12.598626 Done with update 0
2016-01-21 20:24:45.647457 Starting update 1 ([0, 1],) {}
2016-01-21 20:24:45.651199 Adding 0
2016-01-21 20:24:45.652940 Adding 1
2016-01-21 20:24:45.654289 Sleeping
2016-01-21 20:24:50.663665 Done with update 1
2016-01-21 20:24:50.664638 Starting update 2 ([0, 1, 2],) {}
2016-01-21 20:24:50.668122 Adding 0
2016-01-21 20:24:50.669778 Adding 1
2016-01-21 20:24:50.671121 Adding 2
2016-01-21 20:24:50.672656 Sleeping
2016-01-21 20:24:55.682917 Done with update 2
``
The bokeh server will also use significant CPU.
If I do a tcpdump, I see the PATCH-DOC json messages going back and forth between both the client and the server, and between the application and the server. On some runs, I will see runtime errors in the server log:
ERROR:bokeh.server.protocol.server_handler:error handling message Message ‘PATCH-DOC’ (revision 1): RuntimeError(‘Cannot apply patch to 9bf6a048-035f-4d05-8168-3dd9dfd5e889 which is not in the document’,)
DEBUG:bokeh.server.protocol.server_handler: message header {u’msgid’: u’C1299A1D93DD4D7483B1893454CF55AF’, u’msgtype’: u’PATCH-DOC’} content {u’references’: [{u’attributes’: {u’name’: u’title_panel’, u’tags’: }, u’type’: u’LayoutBox’, u’id’: u’LayoutBox-7F186730CFC743078DB472E0DD22F4AB’}], u’events’: [{u’new’: [{u’type’: u’LayoutBox’, u’id’: u’LayoutBox-7F186730CFC743078DB472E0DD22F4AB’}], u’kind’: u’ModelChanged’, u’model’: {u’subtype’: u’Figure’, u’type’: u’Plot’, u’id’: u’9bf6a048-035f-4d05-8168-3dd9dfd5e889’}, u’attr’: u’above’}]}
Traceback (most recent call last):
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/bokeh/server/protocol/server_handler.py”, line 38, in handle
work = yield handler(message, connection)
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/tornado-4.3-py2.7-linux-x86_64.egg/tornado/gen.py”, line 1008, in run
value = future.result()
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/tornado-4.3-py2.7-linux-x86_64.egg/tornado/concurrent.py”, line 232, in result
raise_exc_info(self._exc_info)
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/tornado-4.3-py2.7-linux-x86_64.egg/tornado/gen.py”, line 1017, in run
yielded = self.gen.send(value)
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/bokeh/server/session.py”, line 42, in _needs_document_lock_wrapper
result = yield yield_for_all_futures(func(self, *args, **kwargs))
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/bokeh/server/session.py”, line 212, in _handle_patch
message.apply_to_document(self.document)
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/bokeh/server/protocol/messages/patch_doc.py”, line 80, in apply_to_document
doc.apply_json_patch(self.content)
File “/data/sfw/Python-2.7.6/deploy/lib/python2.7/site-packages/bokeh/document.py”, line 837, in apply_json_patch
raise RuntimeError(“Cannot apply patch to %s which is not in the document” % (str(patched_id)))
RuntimeError: Cannot apply patch to 9bf6a048-035f-4d05-8168-3dd9dfd5e889 which is not in the document
``
And console errors in the client:
Bokeh: Unhandled ERROR reply to C1299A1D93DD4D7483B1893454CF55AF: RuntimeError(‘Cannot apply patch to 9bf6a048-035f-4d05-8168-3dd9dfd5e889 which is not in the document’,)
``
Does this seem like a bug, or am I misusing functionality?
I suspect that I could alleviate this issue to some degree if I re-wrote my update function to do the graph calculation another thread, or to make use of co-routines, but it’s clear to me how best to do this. Any ideas?
Thanks,
Peter