This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new a036dc17024 Fix scheduler ``serve_logs`` subprocess file descriptor 
errors (#55443)
a036dc17024 is described below

commit a036dc17024268b42238741c39f46976e11dcfbb
Author: Kaxil Naik <kaxiln...@apache.org>
AuthorDate: Tue Sep 9 16:15:38 2025 -0600

    Fix scheduler ``serve_logs`` subprocess file descriptor errors (#55443)
    
    Remove workers parameter from `uvicorn.run()` in `serve_logs` to fix file
    descriptor errors when scheduler starts serve_logs as a subprocess.
    The original implementation used `workers=2` (copied from Gunicorn) but
    this caused multiprocessing issues in containerized environments.
    
    Also implemented lazy app loading via `get_app()` function for better
    initialization order and architectural consistency with main API.
    
    The primary fix addresses: OSError: [Errno 9] Bad file descriptor. 
Secondary improvement ensures proper initialization timing.
    
    This regression was introduced in 
[#52581](https://github.com/apache/airflow/pull/52581) when serve_logs was 
refactored from Flask to FastAPI.
    
    ---
    Without this fix, we see following error when running `airflow standalone` 
fresh in an isolated container:
    
    Error 1:
    
    ```
    scheduler  | Traceback (most recent call last):
    scheduler  | File "/.venv/bin/airflow", line 10, in <module>
    scheduler  | sys.exit(main())
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/__main__.py", line 55, in main
    scheduler  | args.func(args)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/cli_config.py", line 49, in 
command
    scheduler  | return func(*args, **kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/cli.py", line 114, in wrapper
    scheduler  | return f(*args, **kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/providers_configuration_loader.py",
 line 54, in wrapped_function
    scheduler  | return func(*args, **kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/commands/scheduler_command.py",
 line 52, in scheduler
    scheduler  | run_command_with_daemon_option(
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/commands/daemon_utils.py", 
line 86, in run_command_with_daemon_option
    scheduler  | callback()
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/commands/scheduler_command.py",
 line 55, in <lambda>
    scheduler  | callback=lambda: _run_scheduler_job(args),
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/commands/scheduler_command.py",
 line 42, in _run_scheduler_job
    scheduler  | with _serve_logs(args.skip_serve_logs), 
_serve_health_check(enable_health_check):
    scheduler  | File "/usr/local/lib/python3.10/contextlib.py", line 135, in 
__enter__
    scheduler  | return next(self.gen)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/cli/commands/scheduler_command.py",
 line 62, in _serve_logs
    scheduler  | from airflow.utils.serve_logs import serve_logs
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/serve_logs/__init__.py", 
line 20, in <module>
    scheduler  | from airflow.utils.serve_logs.log_server import create_app
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/serve_logs/log_server.py", 
line 160, in <module>
    scheduler  | app = create_app()
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/serve_logs/log_server.py", 
line 153, in create_app
    scheduler  | JWTAuthStaticFiles(directory=log_directory, html=False),
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/airflow/utils/serve_logs/log_server.py", 
line 49, in __init__
    scheduler  | super().__init__(*args, **kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/starlette/staticfiles.py", line 56, in 
__init__
    scheduler  | raise RuntimeError(f"Directory '{directory}' does not exist")
    scheduler  | RuntimeError: Directory '/root/airflow/logs' does not exist
    ```
    
    Error 2:
    
    This was because we had harcoded 2 workers! but this isn't needed
    ```
    scheduler  | Process SpawnProcess-1:53:
    scheduler  | Traceback (most recent call last):
    scheduler  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 314, in _bootstrap
    scheduler  | self.run()
    scheduler  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 108, in run
    scheduler  | self._target(*self._args, **self._kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/uvicorn/_subprocess.py", line 73, in 
subprocess_started
    scheduler  | sys.stdin = os.fdopen(stdin_fileno)  # pragma: full coverage
    scheduler  | File "/usr/local/lib/python3.10/os.py", line 1030, in fdopen
    scheduler  | return io.open(fd, mode, buffering, encoding, *args, **kwargs)
    scheduler  | OSError: [Errno 9] Bad file descriptor
    scheduler  | Process SpawnProcess-1:54:
    scheduler  | Traceback (most recent call last):
    scheduler  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 314, in _bootstrap
    scheduler  | self.run()
    scheduler  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 108, in run
    scheduler  | self._target(*self._args, **self._kwargs)
    scheduler  | File 
"/.venv/lib/python3.10/site-packages/uvicorn/_subprocess.py", line 73, in 
subprocess_started
    scheduler  | sys.stdin = os.fdopen(stdin_fileno)  # pragma: full coverage
    scheduler  | File "/usr/local/lib/python3.10/os.py", line 1030, in fdopen
    scheduler  | return io.open(fd, mode, buffering, encoding, *args, **kwargs)
    scheduler  | OSError: [Errno 9] Bad file descriptor
    triggerer  | Process SpawnProcess-1:53:
    triggerer  | Traceback (most recent call last):
    triggerer  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 314, in _bootstrap
    triggerer  | self.run()
    triggerer  | File "/usr/local/lib/python3.10/multiprocessing/process.py", 
line 108, in run
    triggerer  | self._target(*self._args, **self._kwargs)
    triggerer  | File 
"/.venv/lib/python3.10/site-packages/uvicorn/_subprocess.py", line 73, in 
subprocess_started
    triggerer  | sys.stdin = os.fdopen(stdin_fileno)  # pragma: full coverage
    triggerer  | File "/usr/local/lib/python3.10/os.py", line 1030, in fdopen
    triggerer  | return io.open(fd, mode, buffering, encoding, *args, **kwargs)
    triggerer  | OSError: [Errno 9] Bad file descriptor
    ```
---
 airflow-core/src/airflow/utils/serve_logs/core.py       |  7 ++-----
 airflow-core/src/airflow/utils/serve_logs/log_server.py | 10 +++++++++-
 2 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/airflow-core/src/airflow/utils/serve_logs/core.py 
b/airflow-core/src/airflow/utils/serve_logs/core.py
index c7d15f0d240..9f7543d5076 100644
--- a/airflow-core/src/airflow/utils/serve_logs/core.py
+++ b/airflow-core/src/airflow/utils/serve_logs/core.py
@@ -54,11 +54,8 @@ def serve_logs(port=None):
     logger.info("Starting log server on %s", serve_log_uri)
 
     # Use uvicorn directly for ASGI applications
-    uvicorn.run("airflow.utils.serve_logs.log_server:app", host=host, 
port=port, workers=2, log_level="info")
-    # Note: if we want to use more than 1 workers, we **can't** use the 
instance of FastAPI directly
-    # This is way we split the instantiation of log server to a separate module
-    #
-    # 
https://github.com/encode/uvicorn/blob/374bb6764e8d7f34abab0746857db5e3d68ecfdd/docs/deployment/index.md?plain=1#L50-L63
+    uvicorn.run("airflow.utils.serve_logs.log_server:get_app", host=host, 
port=port, log_level="info")
+    # Log serving is I/O bound and has low concurrency, so single process is 
sufficient
 
 
 if __name__ == "__main__":
diff --git a/airflow-core/src/airflow/utils/serve_logs/log_server.py 
b/airflow-core/src/airflow/utils/serve_logs/log_server.py
index fa0338e5c9f..16acd64c538 100644
--- a/airflow-core/src/airflow/utils/serve_logs/log_server.py
+++ b/airflow-core/src/airflow/utils/serve_logs/log_server.py
@@ -157,4 +157,12 @@ def create_app():
     return fastapi_app
 
 
-app = create_app()
+app = None
+
+
+def get_app():
+    """Get or create the FastAPI app instance."""
+    global app
+    if app is None:
+        app = create_app()
+    return app

Reply via email to