Skip to content

bug in pattern-matching background callbacks #3169

Open

Description

@BSd3v
dash                 2.18.2

When using Pattern-Matching callbacks that use background callbacks, the current way things work. If the output is the "same", (same exact callback), it will cancel the first callback and start the new one.

Here is an example of three buttons designed to use pattern-matching to update the Divs above once the task is complete, however, the task never completes if you trigger another button push within the time of the first push:

import dash
from dash import dcc, html, Input, Output, ctx, MATCH, DiskcacheManager, no_update
import time

import diskcache
cache = diskcache.Cache("./cache")

background_callback_manager = DiskcacheManager(cache)

# Initialize the Dash app
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

# Define the layout of the app
app.layout = html.Div([
    html.Div(id='progress-container', children=[
        *[html.Div(id={'type': 'progress', 'index': i}, children=f'{i}') for i in range(3)],
        *[html.Button(id={'type': 'button', 'index': i}, children=f'trigger-{i}') for i in range(3)]
    ]),
    html.Div(id='test-out'),
    html.Button(id='download_test', children='Download Test'),
    dcc.Download(id='download_file')
])

# Pattern-matching callback to update progress
@app.callback(
    Output({'type': 'progress', 'index': MATCH}, 'children'),
    Input({'type': 'button', 'index': MATCH}, 'n_clicks'),
    progress=[Output('test-out', 'children')],
    background=True,
    prevent_initial_call=True
)
def update_progress(set_progress, n):
    if n:
        count = 0
        while count < 100:
            count += 10
            set_progress(f'{ctx.triggered_id.index} - {count}')
            time.sleep(1)
        # Read the existing data from the file
        try:
            with open('test.txt', 'r') as f:
                data = f.readlines()
        except:
            data = []

        # Append the new line to the data
        data.append(f'{ctx.triggered_id.index} - {n}\n')

        # Write the updated data back to the file
        with open('test.txt', 'w') as f:
            f.writelines(data)
        return 'Task Complete!'
    return no_update

@app.callback(
    Output('download_file', 'data'),
    Input('download_test', 'n_clicks'),
    prevent_initial_call=True
)
def downfile(n):
    if n:
        with open('test.txt', 'r') as f:
            data = f.read()  # Read the entire file content
        return dcc.send_bytes(data.encode(), filename='test.txt')
    return no_update


# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

It also doesnt just stop polling for the response from the server, it also completely drops the function call. This is dangerous, especially when dealing with processing in the background that need to complete.

Using the above example, I clicked all three buttons but only the third completed the task fully:

Image


Here is the process that cancels the background running task, maybe we could allow for opting out of this check:

if (cb.callback.output === job.output) {
// Terminate the old jobs that are not completed
// set as outdated for the callback promise to
// resolve and remove after.
additionalArgs.push(['oldJob', job.jobId, true]);
dispatch(
setCallbackJobOutdated({jobId: job.jobId})
);
}

Activity

  1. changed the title [-][BUG] Pattern-Matching Background Callbacks[/-] [+]but in pattern-matching background callbacks[/+] on Feb 18, 2025
  2. added
    bugsomething broken
    P2considered for next cycle
    communitycommunity contribution
    on Feb 18, 2025
  3. gvwilson commented on Feb 18, 2025

    @gvwilson
    Contributor

    Thanks for the detailed report @BSd3v - I'll ask @T4rk1n to take a look when he can.

  4. changed the title [-]but in pattern-matching background callbacks[/-] [+]bug in pattern-matching background callbacks[/+] on Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2considered for next cyclebugsomething brokencommunitycommunity contribution

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions