Improving handling of aborted callbacks

I’ve seen a few people indicate displeasure at the current method of aborting callback execution without returning a response to the client – which is to raise any exception you like in the callback.

The only reason I find this a little strategy a little frustrating is that it pollutes the terminal running the Dash app with an angry looking traceback that I think is a problem every time I see it, only to then see that it was expected.

An improvement on this situation that occurred to me is defining a special exception for halting callbacks and then having Flask catch this exception printing a quieter indication of the event then and returning an empty 204 no response.

I’ve tried this out with a test app that I’ve included below, and it seems to work, without causing the second callback which would otherwise be triggered to fire, and without causing any errors in the client. Could something like this be potentially included into Dash?

import sys

import dash
from dash.dependencies import Input, State, Output
import dash_core_components as dcc
import dash_html_components as html


app = dash.Dash()
app.layout = html.Div([
    html.Div(id='target1'),
    html.Div(id='target2'),
    dcc.Input(id='input', type='text', value=''),
    html.Button(id='submit', n_clicks=0, children='Save')
])

class HaltCallback(Exception):
    pass

@app.server.errorhandler(HaltCallback)
def handle_error(error):
    print(error, file=sys.stderr)
    return ('', 204)

@app.callback(Output('target1', 'children'), [Input('submit', 'n_clicks')],
              [State('input', 'value')])
def callback1(n_clicks, state):
    raise HaltCallback("Halting because of reason foo.")
    return f"callback1: {state}"

@app.callback(Output('target2', 'children'), [Input('target1', 'children')])
def callback2(value):
    return f"callback2: {value}"

if __name__ == '__main__':
    app.run_server(debug=True)
3 Likes

Great idea! I like this solution a lot.

What should the API be?

from dash import Abort
from dash.exceptions import Abort
from dash import Halt
from dash.exceptions import Halt
from dash import PreventUpdate
from dash.exceptions import PreventUpdate

? Any other ideas?

I think I prefer from dash.exceptions import PreventUpdate.

I’d happily accept a PR that does this :+1:

2 Likes

Seems like a good solution. I like the from dash.exceptions import PreventUpdate also.

1 Like

I like that one too. Will try to put together a PR in the next day or so.

1 Like

And here it is! https://github.com/plotly/dash/pull/190

3 Likes

@chriddyp, have you had a chance to look at the changes I’ve made to the PR? Hopefully is in or near to a state where it’s ready to be merged.

Thanks for the update! Taking a look at this now

Thanks I like this solution a lot. However for some reason unknown to me raise PreventUpdate is not as performant as simply doing a raise Exceptionthat is not handled by Dash. I bind a callback to hover over scatterplot points (which should only be active when some options are selected so I will abort the callback otherwise), so it is noticeably laggy using the previous option. But I don’t like the raise Exception either since it prints a lot of traceback information. I hope there is a way to abort the callback with minimal performance penalty.

Hm, I don’t see why this would be the case. Could you create a really small, reproducable example?

I’m also a little confused as why that would be the case. It’s definitely faster using a vanilla raise Exception?

Happy to look into it if you can provide an example as @chriddyp suggested.

1 Like

Hello! Kinda of jumping into the discussion, I have ran into some issues (or unwarned behavior) of this exception, and it may or may not be relate to @jzh 's problem.
I have made a simple dash board and wanted to not erase the graphs when the options were empty (how I ended up here), and all went fine with the proposed solution in principle.
Except (of course haha) that I have a checklist with 1 element and, when I uncheck it it does not get uncheckd (visually), but the value does get updated. That happens as well with a dropdown right bellow it.

It is a different problem (or something I don’t know) but the performace thing may have to do with prevent update blocking the update of all the callbacks subsequent to it, or something of the genre.

It is a different problem (or something I don’t know) but the performace thing may have to do with prevent update blocking the update of all the callbacks subsequent to it, or something of the genre.

If you experience update block while running locally, you can try app.run_server(threaded=True), each update request will get it’s own thread and won’t block the next one.

I have an interval component that I use to update the status info of a sensor system into a html Div at relatively short intervals (preferably <1 second), and if the status has changed, it triggers a sequence of callbacks to update several relatively heavy figures. Otherwise it raises PreventUpdate. Another way to trigger the same update sequence is by clickData of the first figure (for this I have another callback function).

I am facing unexpected(?) behaviour when the callback sequence to update the figures is not yet finished and the interval component already triggers the next callback, raising PreventUpdate. PreventUpdate seemingly prevents also the remaining callbacks of the sequence from being triggered (instead of just passing the callback function attached to the interval component, as I wanted), and the last figures are not updated. I tried @Philippe 's suggestion of app.run_server(threaded=True) with no luck.

Any suggestions how I could make it work?

Raising an exception (any exception, not just PreventUpdate) has the effect of aborting the callback chain. Dependant callbacks won’t fire; this is expected behaviour. So you might need to find another way of achieving the desired functionality you’re after.

Also note that PreventUpdate is only special in that in that it won’t pollute the terminal running the Flask app with exception tracebacks. Raising any Exception will have the same effect otherwise.

1 Like

Thank you for the reply, it makes sense. However, I’m running out of ideas how to achieve the functionality I’m after.

The thing is, I am using Dash as a user interface to a sensor system, and thus want it to have fast response to certain signals coming from the hardware side to guide the user. At the same time, I want to have dependent interactive figures to display readings of the sensors. I am struggling to find away to implement the fast response component (just a html Div showing some text) without interrupting the callback sequence updating the figures.

Interval component is not optimal, because in order to have fast response I need to set the interval short, leading to the problem of terminating the callback sequence if it’s not finished within the interval. I also tried updating the html Div programmatically, and firing the update callback function programmatically, but neither approach works. Lastly I tried to fool the interval component by setting the interval to Inf and programmatically increasing n_intervals but turns out it doesn’t trigger the callback.

I understand my application is not necessarily what Dash is designed for in the first place, but do you have any ideas how I could abuse it to solve my problem?

Are you running your app with multiple processes or threads? app.run_server(threaded=True). Alternatively, run it with multiple workers with gunicorn. That way, the callbacks are handled “in parallel” by multiple workers.