[BUG] Callbacks still double-register with uvicorn `--reload` when an explicit `name` is passed to `Dash()` (incomplete fix for #3818 / #3883)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.4k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 13
Description
Summary
Thanks for the fix! This is a follow up issue from #3818 . PR #3883 (released in 4.4.1) fixes the double-registration reported in #3818 but only for the exact idiom app = Dash(__name__) written at the top level of the entry-point file.
The fix (alias_main_module) keys entirely on the app's resolved name being "__main__" or "__mp_main__". As soon as Dash() receives any other name — a literal string, or Dash(__name__) when the app is built inside a package / app-factory (where __name__ is the module name) — the alias no-ops, the module is executed twice in the reload worker, and every callback is registered twice. The renderer then throws:
Duplicate callback outputs
Output 0 (output.children) is already in use.
Environment
- dash 4.4.1 (
dash[fastapi]) - uvicorn (standard), reload=True
- Python 3.10, Linux
Minimal reproduction
# repro.py
from dash import Dash, html, dcc, callback, Output, Input
# BUG TRIGGER: any explicit name that is not "__main__"/"__mp_main__".
# Using Dash(__name__) here (top level) works; Dash("myapp") does not.
app = Dash("myapp", backend="fastapi")
app.layout = html.Div([dcc.Input(id="input", value="Hello"), html.Div(id="output")])
@callback(Output("output", "children"), Input("input", "value"))
def update(value):
return f"You typed: {value}"
app.enable_dev_tools(debug=True, dev_tools_hot_reload=True)
server = app.server
if __name__ == "__main__":
import uvicorn
uvicorn.run("repro:server", host="127.0.0.1", port=8050, reload=True)
python3 repro.py
curl -s http://127.0.0.1:8050/_dash-dependencies | grep -o '"output.children"' | wc -l
# -> 2 (expected 1)
Open the page in a browser and the console shows Duplicate callback outputs.
Constructing the app inside a package with Dash(__name__) also reproduces it, because __name__ is the package name, not __main__, in this case -- __name__ == "app_factory":
- app_factory.py:
from dash import Dash, html, dcc, callback, Output, Input
class AppBuilder:
def __init__(self):
self.app = Dash(__name__, backend="fastapi")
self.app.layout = html.Div([
html.H1("Reload double-register demo (factory)"),
dcc.Input(id="input", value="Hello"),
html.Div(id="output"),
])
@callback(Output("output", "children"), Input("input", "value"))
def update(value):
return f"You typed: {value}"
def setup(self):
self.app.enable_dev_tools(debug=True, dev_tools_hot_reload=True)
return self.app.server
- main.py:
if __name__ != "__main__":
from app_factory import AppBuilder
server = AppBuilder().setup()
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:server", host="127.0.0.1", port=8050, reload=True)
Root Vause
dash/_utils.py:
def alias_main_module(caller_name: str) -> None:
if caller_name not in ("__main__", "__mp_main__"):
return
...
alias_main_module only aliases the running module into sys.modules when caller_name is exactly "__main__"/"__mp_main__". Passing an explicit name (or building the app anywhere other than the entry module's top level) makes caller_name something else, so the alias is skipped, the "main:server" import string re-executes the file, and the callbacks register a second time.
This means the fix effectively only covers the single-file app = Dash(__name__) tutorial layout, and misses the very common app-factory / class-based / explicitly-named app patterns.
Current Workaround
# Skip the reload worker's __mp_main__ spawn re-exec so callbacks are registered exactly once.
if __name__ not in ("__main__", "__mp_main__"):
server = build_app()
Works temporarily but idk if that will break in any future releases.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dash/_utils.py at alias_main_module and reproduce the issue with the provided explicit-name app and uvicorn reload configuration. Trace how the reload worker imports the server and registers callbacks; done means the dependency output contains each callback once for explicit names and app-factory patterns, without breaking the existing main behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fastapi, python
- Domain
- backend, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100