plotly / plotly/dash

Background callbacks not working on iOS/iPhone and Safari

Open
#3,123 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug cs P2
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

[workaround and code to reproduce the issue at the end of this message]

https://github.com/user-attachments/assets/71d085f6-1377-4559-abca-0e34d720facd

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduction in app.py and the deployment configuration in Procfile and DOKKU_SCALE. Deploy the supplied Celery-backed background callback app, then run it in Safari with the Inspect window open and compare the failing callback with the documented dcc.Interval workaround. Done means background callbacks work reliably without requiring the workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, redis
Domain
backend, devops, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.