Keyboardinterrupt button in voilà

Hi,

First thanks a lot for voilà, I love that tool.

I have a simple dashboard that allows to select options before running a command (calling a python function). I would like to let the user interrupt the computation (ideally with a confirmation step), maybe with a button.

I’m not familiar enough with javascript & jupyter to extract this from the notebook source.

Has anybody done that already?
Thanks for any hint

Best!
Guillaume

I think to do it as gracefully as you describe, where you could then provide feedback to the user, you’d need to tie the function you are calling to a thread, see here.

This example here may help you develop the stop button part. This timer code in a working notebook here that I found in my search attempt on Github illustrates some of the concepts well, too, and may help you integrate what you require. (The first example also works where I suggested running the timer notebook in the gist.)

1 Like

Thanks a lot @fomightez! I think this is indeed the clean way to do it. Having the time consuming computation in a separate thread can also let the user interact with other parts of the dashboard (without getting bored).

I’ll post a snippet with the implemented solution here when it’s ready.

1 Like

So here is what I did (not perfect but it serves its purpose). For the curious, the context is here

from multiprocessing import Process
import ipywdgets as widgets

class MyWidget(widgets.VBox):
    def __init__(self):
        ...
        self.run_btn = widgets.Button(description="run")
        self.run_btn.button_style = "info"
        self.run_btn.on_click(self.run)

        self.interupt_button = widgets.Button(description="stop")
        self.interupt_button.on_click(self.stop)
        self.current_process = Process(target=lambda: print("Not set"))
        ...

    def run(self, btn):

        if self.current_process.is_alive():
            return

        self.current_process.close()
        with self.output:
            if not self.kwargs:
                self.run_btn.button_style = "danger"
                self.run_btn.description = "errored"
                return
            self.run_btn.description = "running ..."
            self.run_btn.button_style = "warning"
            self.current_process = Process(target=a_long_job, name="main")
            self.current_process.start()
            self.output.clear_output()
            return 0

    def stop(self, btn):
        if not self.current_process.is_alive():
            return

        self.run_btn.description = "stopping"
        self.current_process.terminate()
        self.current_process.join()
        self.current_process.close()
        self.run_btn.description = "stopped"
        self.run_btn.button_style = "danger"

I tried +/- the same code with a Thread instead of a Process but could not get signal.pthread_kill to behave like I wanted to.

There is a strange behavior with notebook restart (the kernel crashes when I try to restart the notebook after I stopped the process). I assume it is because there are hanging threads somewhere, but could not find a solution to that.

Thanks again for the help!

1 Like