Interrupting a slow callback from another callback

from bokeh.models.widgets import Button
from bokeh.layouts import column
from bokeh.models import Div
from bokeh.io import curdoc
import time

def srch_kwrds1():
    time.sleep(30)

def srch_kwrds():
    layout.children.insert(1, srch_txt)
    layout.children.insert(2, stp) 
    curdoc().add_next_tick_callback(srch_kwrds1)

def stp_srch():
    layout.children.remove(srch_txt)
    layout.children.remove(stp)
    # how do I break out of srch_kwrds1?

srch = Button(label='search')
srch.on_click(srch_kwrds)

srch_txt = Div(text='searching')

stp = Button(label='stop')
stp.on_click(stp_srch)

layout = column(srch)

curdoc().add_root(layout)

Could you please help me with the easiest way to interrupt the callback srch_kwrds1 by clicking on the stp button?

There’s no general answer to this. The details depend very much on the specifics of the actual work you will be doing. First. the work in srch_kwrds1 has to actually be interruptible, which is not the case with time.sleep. In a real scenario you would need work that can done incrementally in a loop that checks each iteration for a flag that indicates to stop, or else need some external thread or process that is poll-able and can be cancelled or shut down from outside.

Thanks for your reply @Bryan.

srch_kwrds1 calls some other functions that call others in turn and I’m not sure which function would be getting executed when the user clicks the stop button, stp.

Is there a way to make the function srch_kwrds1 interruptible as a whole as opposed to trying to interrupt a part of it like time.sleep in this example?

For example (not sure if this makes sense), can I make curdoc().add_next_tick_callback(srch_kwrds1) run srch_kwrds1 asynchronously and get the process id so that it can be killed when the stop button is clicked?

@PSK You could certainly start work in either a thread or subprocess (I assume that is what you mean by “asynchronously”) and then kill the process or thread. Details how to do that is not really a Bokeh question, though, you might find a larger pool of potential answerers on Stack Overflow

Got it. Thanks a lot @Bryan!

1 Like

Hi @Bryan, I tried one possible solution and now I have a new problem. I wasn’t sure if I should open a new thread or ask here so sorry if I should’ve opened a new one.

from bokeh.models.widgets import Button, Div
from bokeh.layouts import column
from bokeh.io import curdoc
import time
from multiprocessing import Process


class Callbacks:
    def __init__(self):
        self.i = 0

    def srch_kwrds1(self):
        time.sleep(5)
        layout.children[2] = Div(text='done!') 
        print(self.i + 1)

    def srch_kwrds(self):
        layout.children.insert(2, srch_txt)
        self.p = Process(target=self.srch_kwrds1) 
        self.p.start()
        
    def stp_srch(self):
        self.p.terminate()
        srch_txt.text = 'stopped'
        print(self.i)


cb = Callbacks()

srch = Button(label='search')
srch.on_click(cb.srch_kwrds)

stp = Button(label='stop')
stp.on_click(cb.stp_srch)

srch_txt = Div(text='searching')

layout = column(srch, stp)

curdoc().add_root(layout)

Now, I can interrupt srch_kwrds1 but my layout doesn’t get updated. Why is that? Can I do anything to update it?

Process launches a completely new Python process. There’s no IPC that would make the changes to Bokeh models in the child process be visible by the parent process.
When using multiprocessing, you usually have to either create some side-effects (create files, print something, query some web servers etc) or communicate some plain data using queues or pipes: multiprocessing — Process-based parallelism — Python 3.10.0 documentation

If you have some data-processing pipeline that works for a long time, consider using joblib or dask - they’re much easier to use than multiprocessing.

Thanks @p-himik for the suggestion about dask and joblib. With regards to what I am trying, I have asked a question on stackoverflow: How to terminate a process using multiprocessing in Python? - Stack Overflow but I think even if I get it to work, it would create more problems for me to deal with like orphaned processes etc. so I am going to leave this alone for now. If I happen to find a solution that works for me I will post it here.