When an async background callback (background=True with an async def function) raises an exception or PreventUpdate, the worker fails with:
UnboundLocalError: cannot access local variable 'user_callback_output' where it is not associated with a value
This masks the original error, so the real cause of the failure is never surfaced to the on_error handlers or the client.
Affected code
Both background callback managers have the bug in their async_run() path:
dash/background_callback/managers/celery_manager.pydash/background_callback/managers/diskcache_manager.py
In async_run(), user_callback_output is only assigned inside the try block. When the callback raises, control jumps to the except block (which sets errored = True), then reaches:
ifasyncio.iscoroutine(user_callback_output):
user_callback_output=awaituser_callback_output
Since user_callback_output was never assigned, this raises UnboundLocalError. The synchronous run() path does not have this bug because it initialises user_callback_output = None before the try.
Steps to reproduce
fromdashimportDash, Input, Output, html, callbackfromdash.exceptionsimportPreventUpdate# ... configure a CeleryManager or DiskcacheManager as background_callback_manager@callback(Output("out", "children"),Input("btn", "n_clicks"),background=True,prevent_initial_call=True,)asyncdefhandler(_):
raisePreventUpdate# or: raise Exception("boom")Clicking the button raises UnboundLocalError in the worker instead of cleanly preventing the update / reporting the error.
Expected behaviour
An async background callback that raises PreventUpdate or an exception should behave like the synchronous case: the no-update result or background_callback_error is reported, and the original error is not masked.
Environment
- dash:
4.2.0 - Python:
3.12 - Manager: Celery (and Diskcache — both affected)
When an async background callback (
background=Truewith anasync deffunction) raises an exception orPreventUpdate, the worker fails with:This masks the original error, so the real cause of the failure is never surfaced to the
on_errorhandlers or the client.Affected code
Both background callback managers have the bug in their
async_run()path:dash/background_callback/managers/celery_manager.pydash/background_callback/managers/diskcache_manager.pyIn
async_run(),user_callback_outputis only assigned inside thetryblock. When the callback raises, control jumps to theexceptblock (which setserrored = True), then reaches:Since
user_callback_outputwas never assigned, this raisesUnboundLocalError. The synchronousrun()path does not have this bug because it initialisesuser_callback_output = Nonebefore thetry.Steps to reproduce
Clicking the button raises
UnboundLocalErrorin the worker instead of cleanly preventing the update / reporting the error.Expected behaviour
An async background callback that raises
PreventUpdateor an exception should behave like the synchronous case: the no-update result orbackground_callback_erroris reported, and the original error is not masked.Environment
4.2.03.12