Eagerly import the ASGI app in the parent process#2919
Merged
Conversation
Splits Config.load() into a small load_app() (imports the app, sets loaded_app) plus the rest, and calls config.load_app() in uvicorn.run() before the supervisor or Server is constructed. This: - Fails fast in the parent on a bad app path; no more infinite worker restart loop with --workers > 1. - Imports the app synchronously before any event loop starts, so apps that do `app = asyncio.run(...)` at module load time no longer crash with "loop already running". - Primes sys.modules in the parent, so future thread workers (#2900) see a cache hit in load_app() instead of racing on the importlib global lock. The spawn child must still re-import (fresh interpreter, possibly non-picklable loaded_app) - _subprocess.get_subprocess() shallow-copies the Config and clears loaded/loaded_app on the copy before pickling. A TODO points at the deeper fix: splitting Config (settings) from the runtime state Server actually needs. Config.load() is now idempotent (early-return when self.loaded), so defensive `if not config.loaded: config.load()` guards in Server, LifespanOn, and the websockets sans-io protocol stay correct. Closes #941 Closes #2440 Supersedes #942 Supersedes #2444
Contributor
Codex review caught a real bug: storing loaded_app on the parent's Config breaks spawn pickling for non-picklable apps (DB pools, locks, etc.) because Multiprocess passes server.run as a bound method, and that bound method captures the same Config object. Stripping loaded_app from the kwargs Config copy doesn't help - the bound method still drags the original. Fix: in uvicorn.run(), import the app for fail-fast validation and discard the result. Workers re-import freshly via Server.run -> config.load(). The parent never carries loaded_app, so the bound-method pickle is safe. Removes the _subprocess.py shallow-copy hack (no longer needed) and the test that asserted on it. The #941 regression test moved from test_config.py to test_main.py, where it now verifies via Server.run patching that the import lands in sys.modules before Server.run is called.
3 tasks
harupy
added a commit
to mlflow/mlflow
that referenced
this pull request
May 25, 2026
Uvicorn 0.47.0 eagerly imports the ASGI app in the parent process before forking workers (Kludex/uvicorn#2919). This appears to delay worker readiness on CI runners, causing the 10s `/health` poll in tests/webhooks/test_e2e.py and tests/tracking/test_rest_tracking.py to time out. Pin to verify. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: harupy <17039389+harupy@users.noreply.github.com>
31 tasks
harupy
added a commit
to harupy/mlflow
that referenced
this pull request
May 26, 2026
uvicorn 0.47 adds a serialized parent-process import of the ASGI app to the multi-worker startup path (Kludex/uvicorn#2919), pushing `mlflow server` cold-start past the prior 10-attempt (~10-30s) budget on CI. 30 attempts gives a 30-90s ceiling, which absorbs the new latency while still failing fast on a genuinely broken server. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: harupy <17039389+harupy@users.noreply.github.com>
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits
Config.load()so the app import is reusable in isolation, and calls it eagerly fromuvicorn.run()before the supervisor orServeris constructed.Config.load_app()imports the app viaimport_from_stringand returns it. No state mutation.Config.load()callsself.loaded_app = self.load_app()for the existing back-compat behavior.uvicorn.run()callsconfig.load_app()and discards the return value: enough to fail fast on a bad import string, without leaving anything on the parent'sConfigfor spawn workers to pick up.This:
--workers > 1.app = asyncio.run(...)at module load time no longer crash withRuntimeError: this event loop is already running.sys.modulesin the parent, so future thread workers (Add free-threaded thread worker class #2900) see a cache hit when each thread runs its ownConfig.load()instead of racing on the importlib global lock for the initial import.Spawn workers are unaffected:
uvicorn.run()doesn't store the imported app onConfig, so the boundserver.runmethod thatMultiprocessships across pickle never carries it. Workers re-import inServer.run -> Config.load.Closes / supersedes
Config#loadout ofServer#serveto enable Eager Asynchronous Construction of App #942Test plan
uv run pytestpasses locally (tests/test_config.py::test_socket_bindexcluded due to a port collision unrelated to this change).uv run mypy uvicornclean.uvicorn.run()is reverted:tests/test_main.py::test_run_fails_fast_in_parent_on_bad_app_path(Reduce config load and increase priority #2440)tests/test_main.py::test_run_imports_app_before_starting_event_loop(Eager Asynchronous Construction of App Interferes with Running Event Loop #941)AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.