Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug in pattern-matching background callbacks #3169

Open
BSd3v opened this issue Feb 14, 2025 · 1 comment
Open

bug in pattern-matching background callbacks #3169

BSd3v opened this issue Feb 14, 2025 · 1 comment
Assignees
Labels
bug something broken community community contribution P2 considered for next cycle

Comments

@BSd3v
Copy link
Contributor

BSd3v commented Feb 14, 2025

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})
);
}

@gvwilson gvwilson changed the title [BUG] Pattern-Matching Background Callbacks but in pattern-matching background callbacks Feb 18, 2025
@gvwilson gvwilson added bug something broken P2 considered for next cycle community community contribution labels Feb 18, 2025
@gvwilson
Copy link
Contributor

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

@BSd3v BSd3v changed the title but in pattern-matching background callbacks bug in pattern-matching background callbacks Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken community community contribution P2 considered for next cycle
Projects
None yet
Development

No branches or pull requests

3 participants