Proper way to remove a callback from a document

Is there a proper way to remove a callback from a document? I tried to follow the structure of the old Gapminder example but haven’t been able to stop the animation when it ends.

webinstance = curdoc()
index = 1
callback_id = None
def modify_doc(doc):
	X = np.linspace(-2*np.pi, 2*np.pi, N)
	Y = 0.5 * np.sin(3 * X)

	p = figure(plot_height=500, plot_width=1000, title="Test", x_range=(-2*np.pi,2*np.pi), y_range=(-0.6,0.6))

	ypts = ColumnDataSource(data=dict(x=[X[0]], y=[Y[0]]))"x", y="y", size=9, source=ypts)

	def update():
		global index
		global callback_id  
		new_data = dict(x=[X[index]], y=[Y[index]])
		index += 1

	if index < N-1:
		callback_id = doc.add_periodic_callback(update, 40)

	doc.title = "Test"


Can you provide a complete minimal example that demonstrates what you are seeing that is unexpected, that can be run and investigated? It’s really hard to say much about partial code. It looks reasonable at a glance.

Ah. Thank you for reminding me. Yes, perhaps my questions should be: is this normal? :grin:

In terminal, after printing the index, so at the 100 frame, is gives the below error for every callback. The index error is puzzling because I tried backing way off the set limit to see if that was the problem and it never triggered at all. I think I’m misunderstanding something about the nature of index errors.

2019-08-09 17:44:02,636 Traceback (most recent call last):
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/tornado/", line 501, in callback
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/tornado/", line 742, in run
    yielded = self.gen.send(value)
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/bokeh/server/", line 70, in _needs_document_lock_wrapper
    result = yield yield_for_all_futures(func(self, *args, **kwargs))
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/bokeh/server/", line 191, in with_document_locked
    return func(*args, **kwargs)
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/bokeh/document/", line 1126, in wrapper
    return doc._with_self_as_curdoc(invoke)
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/bokeh/document/", line 1112, in _with_self_as_curdoc
    return f()
  File "/anaconda3/envs/EarlyTesting/lib/python3.7/site-packages/bokeh/document/", line 1125, in invoke
    return f(*args, **kwargs)
  File "/anaconda3/envs/EarlyTesting/", line 28, in update
    new_data = dict(x=[X[index]], y=[Y[index]])
IndexError: index 100 is out of bounds for axis 0 with size 100

I’m afraid you’ve lost me—I don’t see how this reply relates to the original question in any way. To answer this new question, though: that exception indicates that while you are trying to access X[100] and Y[100], at least one of X or Y has fewer than 100 elements.

My reasoning is that had I removed the callback correctly, it would not be giving an index error. Perhaps I should edit the post to express that better?

What I am really after is a small (minimal), complete, self-contained reproducer that I can copy and paste and immediately run without any modifications at all. It is often the case that if I can simply run some code, then I can identify the actual issue in seconds. E.g I don’t recall whether callback removal is synchronous or whether a chunk of work is put on the IOLoop to do it— in which case another iteration of the callback might already be queued up to execute first. Having real code to run is the fatest, most efficient way for me to be able to interrogate these possibilities.

Alternatively, if you just want to look in to things more carefully yourself, I’d suggest catching the exception (and passing) so you can observe whether the print statements in the Bokeh server console log output keep increasing indefinitely (bokeh bug?) or whether is stops but just not soon enough (race condition?) or something else.

The modify_doc code is only executed once, when the session is created. Once the session is displayed in a browser, the only code that is subsequently executed in the callbacks that you set up initially. It follows, then, that for the call to remove_callback to have any effect, it needs to be in a callback:

    def update():
        global index
        if index == N:
        new_data = dict(x=[X[index]], y=[Y[index]])
        index += 1

    callback_id = doc.add_periodic_callback(update, 40)

This is the same structure (remove inside the callback) as the Gaminder example you initially reference.

As an aside: I’m not trying to harp on the complete, minimal repropducer thing, but there is literally nothing better that users can do to help others, help them (this answer was evident 10 seconds after actually running the complete code you posted on SO—if you’d posted the complete code I asked for here two days ago, I would have had you an answer two days ago)

I really appreciate you following up.

My delay in responding was because I realized I had made some deeply flawed assumptions about the code I posted and people’s ability to recreate it. I actually reached out to one of the local developer slack channels I’m on (referenced below), pointing to this thread, asking some of my friends for help because my misunderstanding had so much to do with my inexperience in Python I really didn’t want to waste anymore of your time. I still really really appreciate your help, and am working your suggesting into the script this morning along side a great cup of coffee. :grin:

BBirdsell 11:08 AM

Ah? If anyone has a moment? I’m really stuck with this Bokeh/Python callback issue. I’m trying to remove a callback now. I’m really stuck and need to raise my hand for help. This gentleman I’m conversing with in the thread is so nice. But I also know he is sooo busy. How can I make this question better to ask on stack overflow?

1 Like