Upload component + Dash 2.0 long callback issue

Hi everyone,
so I’m developing an ML/CV app where users can upload images. As processing those usually takes some time, I was excited to try out dash 2.0’s long callbacks to keep users up to date and show intermediate results while the process is running. Everything worked fine on my local machine, but when I deployed to our network, I faced the issue that the app stopped working when uploading too many or too large files (like ~10MB, depending on network). My observations:

  • Frontend stayed in “running” state forever, though callback seemed to be finished in backend
  • pending _dash-update-component requests piling up in network tab, in the end, my browser went oom
  • Network traffic (upload) goes nuts

Here’s a minimum example: I can reproduce the issue by uploading a file which is large enough (upload taking more than 1s, maybe?), but small enough to be accepted by the server. Isssue appreared in both Chrome and Firefox.

from dash import dcc, html, Dash, Input, State, Output
from dash.long_callback import CeleryLongCallbackManager
from celery import Celery
from time import sleep

celery_app = Celery(
    __name__, broker="redis://localhost:6379/0", backend="redis://localhost:6379/1"
)
long_callback_manager = CeleryLongCallbackManager(celery_app)

app = Dash(__name__, long_callback_manager=long_callback_manager)

app.layout = html.Div([
    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
    ),
    html.Progress(id="progress_bar", style=dict(visibility='hidden')),
])


@app.long_callback(Input('upload-data', 'contents'),
                   State('upload-data', 'filename'),
                   manager=long_callback_manager,
                   prevent_initial_call=True,
                   running=[
                       (
                               Output("progress_bar", "style"),
                               {"visibility": "visible"},
                               {"visibility": "hidden"},
                       ),

                   ])
def update_output(contents, file_names):
    sleep(5)
    return list()


if __name__ == '__main__':
    app.run_server()

Using a normal callback seems to work as expected. Any help is greatly appreciated! :slight_smile:

Hi @viking ,

I’ll try to take a closer look soon, but one thing to try is to increase the polling interval. The default is 1s, but could you try setting it to something like 3s and see if that makes a difference.

It looks like this didn’t make it into the long_callback docstring, but you can set @app.long_callback(..., interval=3000) to increase the polling interval to 3 seconds.

-Jon

Hi Jon, thank you very much for looking into it!
I wasn’t aware that the polling interval can be customized, good hint. Increasing the interval did increase the amount of data / number of images I’m able to upload. The issue still occurs with sufficiently large uploads or bad network connection, however.

1 Like

Ok, I did some more investigating

-Jon

Isn’t this just an effect of a more general problem related directly to the dcc.Iterval component? If the Interval is triggering a callback whose duration is longer than the interval property of the Interval component, the callback will never be fully executed.

Furthermore, if this callback is supposed to disable the Interval under certain circumstances, the Interval will never be disabled and the app will get stuck in a loop. It has been discussed here.

Hi @sislvacl

Looks like it; thanks for pointing that out. I wasn’t aware of the fact that long callbacks use an interval component under the hood. However, I didn’t find a neat solution here: Since the interval seems to trigger the upload over and over again, we’d certainly need to suppress this client-side to avoid the horrendous amount of network traffic generated by the repeated parallel uploads.