Skip to content

Background callbacks not working on iOS/iPhone and Safari #3123

Open
@celia-lm

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/or cancel 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

Metadata

Assignees

No one assigned

    Labels

    P2considered for next cyclebugsomething brokencscustomer success

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions