Shutdown hook in Panel?

Hello, My Panel application uses a library that creates threads to perform periodic tasks in the background. Is there a way to register a shutdown hook in Panel so I can stop the background tasks before exiting? Currently I have to kill -9 the application to exit, which is not very nice. Thanks in advance for any suggestions.

1 Like

Hi @szampier

I don’t have the answer, but I would see if there is an answer in

Thank you @Marc!

The link you sent on setting up manual threading is very useful but it also shows the lack of a global on_exit callback. The Session Thread example uses pn.state.on_session_destroyed callback to terminate the threads, but there is no such callback in the Global Thread case. Interestingly, the GlobalTaskRunner class defines a remove_all class method which is never invoked. The workaround is to use daemon threads which are abruptly stopped at shutdown but this is not always desirable if one wants to gracefully terminate ongoing tasks or release resources when the application exits.

1 Like

@szampier I think you understanding threading better than I do :smile:. I wrote the guide to at least have some starting point for users.

It would benefit Panel and its users greatly if you created a Feature Request with the needs you have including callback and documentation. If you are up for it a PR to improve the how-to guide, it would be greatly appreciated.

For example daemon threads was the workaround I could find to make the server shut down when I wanted to stop it. But I have never understood what they are.

Hi @Marc, I’m not sure if I can improve the how-to guide which is already very well done and super useful like the rest of the Panel documentation. However, I would like to suggest adding a new on_exit method to the state class to register custom callbacks to be called when the server exits. Below is a sketch of a possible implementation that I have tested locally and it seems to work fine.

# module state.py
class _state(param.Parameterized):
    ...
    _on_exit: ClassVar[list[Callable[[], None]]] = []
   ...
    def on_exit(self, callback):
        self._on_exit.append(callback)
# module server.py
class Server(BokehServer):
    ...
    def stop(self, wait: bool = True) -> None:
        ...
        if state._on_exit:
            for callback in state._on_exit:
                callback()
        super().stop(wait=wait)
# application
pn.state.on_exit(GlobalTaskRunner.remove_all)
1 Like

That seems really useful. Could you create a Feature Request on Github? And reference this discussion? Thanks.

1 Like

Curious, could you use atexit for this?

Hi @Hoxbro, atexit waits for all non-daemon threads to finish before calling the cleanup functions, so unfortunately it won’t work in the situation described in this topic. Also note that the Bokeh server used by Panel already registers a cleanup function with atexit, which calls the stop() method mentioned above.