Skip to content

[BUG] UnboundLocalError: user_callback_output in async background callbacks when callback raises an exception or PreventUpdate #3821

@i-murray

Description

@i-murray

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.py
  • dash/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:

if asyncio.iscoroutine(user_callback_output):
    user_callback_output = await user_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

from dash import Dash, Input, Output, html, callback
from dash.exceptions import PreventUpdate
# ... configure a CeleryManager or DiskcacheManager as background_callback_manager

@callback(
    Output("out", "children"),
    Input("btn", "n_clicks"),
    background=True,
    prevent_initial_call=True,
)
async def handler(_):
    raise PreventUpdate   # 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions