Background callbacks not working on iOS/iPhone and Safari #3123
Open
Description
[workaround and code to reproduce the issue at the end of this message]
Screen.Recording.2025-01-15.at.14.22.34.mov
Text of the error:
[Error] Failed to load resource: The network connection was lost. (_dash-update-component, line 0)
[Error] Error: Callback failed: the server did not respond.
g — dash_renderer.v2_18_2m1736947227.min.js:2:145898
So (dash_renderer.v2_18_2m1736947227.min.js:2:95216)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:103633)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:206857)
p (dash_renderer.v2_18_2m1736947227.min.js:2:42868)
Yi (dash_renderer.v2_18_2m1736947227.min.js:2:115653)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:160719)
forEach
observer (dash_renderer.v2_18_2m1736947227.min.js:2:157858)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:105928)
forEach
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:105879)
p (dash_renderer.v2_18_2m1736947227.min.js:2:42928)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:170393)
f (dash_renderer.v2_18_2m1736947227.min.js:2:161605)
(anonymous function) (dash_renderer.v2_18_2m1736947227.min.js:2:162948)
$u (dash_renderer.v2_18_2m1736947227.min.js:2:168505)
a (dash_renderer.v2_18_2m1736947227.min.js:2:170597)
The issue is intermittent: it's possible that some of the requests/clicks work, but typically if the first one hasn't worked, none of them will.
Information about the video:
- MacBook Ait M3 macOS 14.6.1
- Safari 17.6
- App deployed to Dash Enterprise 4 > I'm not sure if this happens on DE5 or not. I remember replicating it on DE5 this morning but now the same code doesn't cause the issue on DE5.
Things I have not tested:
- If the issue happens locally.
Things I have tested:
The issue still happens:
- If the background callback uses the
running
and/orcancel
arguments. - If we use previous versions of Dash (tested from 2.13 to 2.18).
- The user accesses the app from a Safari incognito window.
Workaround
- Add a dcc.Interval to the layout with a quite frequent refresh rate (I've used interval=200, that is, 200ms > a higher value might work).
- Create a callback that uses that interval as Input. It can have no output and return nothing, like this:
@callback(Input("interval_dummy", "n_intervals"))
def dummycb(_):
return
- Since this workaround will make a callback run every 200ms, the tab name will constantly show "Updating...". You can disable this by specifying updating_title=None as an argument to app = Dash().
Code to reproduce the issue
- Deploy the app.
- Access it from Safari.
- Open the Inspect window.
- Click the "Run" button.
requirements.txt
celery
dash
gunicorn
redis
at the time of creation of this ticket, these are the versions my app uses:
- celery 5.4.0
- dash 2.18.2
- gunicorn 23.0.0
- redis 5.2.1
app.py
import time
import os
import datetime
import dash
from dash import Dash, CeleryManager
from dash import Input, Output, State, ctx, html, dcc, callback #, set_props
from celery import Celery
celery_app = Celery(__name__, broker=f"{os.environ['REDIS_URL']}/5", backend=f"{os.environ['REDIS_URL']}/6")
background_callback_manager = CeleryManager(celery_app)
app = Dash()
server = app.server
app.layout = html.Div(
[
html.Div([html.P(id="paragraph_id", children=["Button not clicked"])]),
html.Button(id="button_id", children="Run Job!"),
html.Button(id="cancel_button_id", children="Cancel Running Job!"),
# dcc.Interval(id="interval_dummy", interval=200),
]
)
@callback(
output=Output("paragraph_id", "children"),
inputs=Input("button_id", "n_clicks"),
background=True,
manager=background_callback_manager
)
def update_clicks(n_clicks):
time.sleep(2.0)
return [f"Clicked {n_clicks} times"]
# @callback(
# Input("interval_dummy", "n_intervals")
# )
# def dummycb(_):
# return
if __name__ == "__main__":
app.run(debug=True)
Procfile
web: gunicorn app:server --workers 4
worker: celery -A app:celery_app worker --loglevel=INFO --concurrency=2
DOKKU_SCALE
web=1
worker=1