plotly / plotly/dash

background callback with MATCH is cancelled by future callbacks with different component ids

Open
#2,681 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3 sev-2
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

Describe your context

dash                        2.14.1
dash-bootstrap-components   1.5.0
dash-core-components        2.0.0
dash-extensions             1.0.3
dash-html-components        2.0.0
dash-iconify                0.1.2
dash-mantine-components     0.12.1
dash-table                  5.0.0
dash-uploader               0.7.0a1

Describe the bug
If a background callback using pattern matching with MATCH is triggered twice by two different objects (and therefore different values for MATCH), the first callback will be cancelled.

This only applies to background callbacks. "Normal" callbacks work fine.

Expected behavior
Both callbacks should finish execution and return their outputs, just like non-background callbacks. (At least if their IDs are different)

MWE
Here's a small example to reproduce the problem:

import os
import time

import diskcache
import dash
from dash import Dash, html, Output, Input, MATCH, DiskcacheManager
from dash.exceptions import PreventUpdate


def layout():
    return html.Div(
        [
            *[html.Button(f"Update {i}", id=dict(type="button", index=i)) for i in range(5)],
            *[html.P(id=dict(type="text", index=i)) for i in range(5)],
        ]
    )


@app.callback(
    Output(dict(type="text", index=MATCH), "children"),
    Input(dict(type="button", index=MATCH), "n_clicks"),
    background=True,
    prevent_initial_call=True,
)
def show_text(n_clicks: int):
    if not n_clicks:
        raise PreventUpdate

    index = dash.ctx.triggered_id["index"]
    print(f"started {index}")
    time.sleep(3)

    print(f"stopped {index}")
    return str(index)


if __name__ == "__main__":
    cache = diskcache.Cache(os.path.join(os.getcwd(), ".cache"))
    app = Dash(background_callback_manager=DiskcacheManager(cache))
    app.layout = layout()
    app.run(host="0.0.0.0", port=8010)

If you click on multiple buttons (within 3 seconds after the last click), the previous execution of the callback will be canceled and only the id of the last button will be shown.

Sample output:

started 0
started 1
started 2
started 3
started 4
stopped 4

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the provided MWE with DiskcacheManager and pattern-matching MATCH callbacks, then trace how background executions are identified and cancelled. Compare the behavior for distinct component IDs with normal callbacks. Done means concurrent background callbacks for different MATCH values both finish and return their outputs, with a regression test covering the case.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.