diff --git a/api/docker-entrypoint.sh b/api/docker-entrypoint.sh index 9b2964b479..e077a3bfd6 100755 --- a/api/docker-entrypoint.sh +++ b/api/docker-entrypoint.sh @@ -21,13 +21,19 @@ apply_fixtures() { } start_dev_server() { - echo "Starting the development server..." - exec uv run python manage.py runserver 0.0.0.0:"${DJANGO_PORT:-8080}" + echo "Starting the development server (Gunicorn ASGI, debug + reload)..." + # Same server/worker as prod (config.asgi via the native `asgi` worker), so + # SSE streams run on the event loop exactly as they do in production. DEBUG is + # on so guniconf's `reload = DEBUG` hot-reloads edited code (and flips + # `preload_app` off so reload actually takes). + export DJANGO_DEBUG="${DJANGO_DEBUG:-True}" + export DJANGO_BIND_ADDRESS="${DJANGO_BIND_ADDRESS:-0.0.0.0}" + exec uv run gunicorn -c config/guniconf.py config.asgi:application } start_prod_server() { echo "Starting the Gunicorn server..." - exec uv run gunicorn -c config/guniconf.py config.wsgi:application + exec uv run gunicorn -c config/guniconf.py config.asgi:application } resolve_worker_hostname() { diff --git a/api/src/backend/config/guniconf.py b/api/src/backend/config/guniconf.py index a16c8de9a0..6f07913e81 100644 --- a/api/src/backend/config/guniconf.py +++ b/api/src/backend/config/guniconf.py @@ -25,6 +25,15 @@ bind = f"{BIND_ADDRESS}:{PORT}" workers = env.int("DJANGO_WORKERS", default=multiprocessing.cpu_count() * 2 + 1) reload = DEBUG +# Native ASGI worker (gunicorn 24+). Required so SSE endpoints can keep the +# event loop alive while waiting for events. +worker_class = env("DJANGO_WORKER_CLASS", default="asgi") + +# Preload the application before forking workers in production: the app is +# imported once in the master and workers fork from it. In development, disable +# preload so the server restarts on code changes. +preload_app = not DEBUG + # Logging logconfig_dict = DJANGO_LOGGERS gunicorn_logger = logging.getLogger(BackendLogger.GUNICORN)