You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by tu...@apache.org on 2026/04/25 12:59:42 UTC

(airflow) branch main updated: Fix Celery workers not executing tasks when --celery-hostname is set (#65826)

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

dheerajturaga 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 ead7a1a17f8 Fix Celery workers not executing tasks when --celery-hostname is set (#65826)
ead7a1a17f8 is described below

commit ead7a1a17f89538daf72c24ae74a90bfd5d1a767
Author: Dheeraj Turaga <dh...@gmail.com>
AuthorDate: Sat Apr 25 07:59:30 2026 -0500

    Fix Celery workers not executing tasks when --celery-hostname is set (#65826)
---
 .../airflow/providers/celery/cli/celery_command.py | 26 ++++++++++-----
 .../tests/unit/celery/cli/test_celery_command.py   | 37 ++++++++++++++--------
 2 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/providers/celery/src/airflow/providers/celery/cli/celery_command.py b/providers/celery/src/airflow/providers/celery/cli/celery_command.py
index 685163042b8..0e2b66aabf1 100644
--- a/providers/celery/src/airflow/providers/celery/cli/celery_command.py
+++ b/providers/celery/src/airflow/providers/celery/cli/celery_command.py
@@ -25,9 +25,10 @@ import time
 from contextlib import contextmanager, suppress
 from multiprocessing import Process
 
+import kombu.pools
 import psutil
 import sqlalchemy.exc
-from celery import maybe_patch_concurrency
+from celery import Celery, maybe_patch_concurrency
 from celery.app.defaults import DEFAULT_TASK_LOG_FMT
 from celery.app.log import TaskFormatter
 from celery.signals import after_setup_logger
@@ -219,18 +220,27 @@ def worker(args):
     # Use team_config for config reads in multi-team mode, otherwise use global conf
     config = team_config if team_config else conf
 
-    # Check if a worker with the same hostname already exists
+    # Check if a worker with the same hostname already exists. The inspect
+    # call must run on a throwaway Celery app, not on the app handed to
+    # worker_main below: inspect opens broker connections and initializes
+    # app.amqp.producer_pool / connection_pool, and kombu.pools is a
+    # process-global registry keyed by broker URL — any pool state that
+    # leaks into the real app breaks the prefork worker's dispatch path
+    # so tasks are received but never run. The kombu.pools.reset() call
+    # is load-bearing; see https://github.com/apache/airflow/issues/59707.
     if args.celery_hostname:
-        inspect = celery_app.control.inspect()
-        active_workers = inspect.active_queues()
-        if active_workers:
-            active_worker_names = list(active_workers.keys())
-            # Check if any worker ends with @hostname
-            if any(name.endswith(f"@{args.celery_hostname}") for name in active_worker_names):
+        temp_app = Celery()
+        temp_app.conf.update(celery_app.conf)
+        try:
+            active_workers = temp_app.control.inspect().active_queues()
+            if active_workers and any(name.endswith(f"@{args.celery_hostname}") for name in active_workers):
                 raise SystemExit(
                     f"Error: A worker with hostname '{args.celery_hostname}' is already running. "
                     "Please use a different hostname or stop the existing worker first."
                 )
+        finally:
+            temp_app.close()
+            kombu.pools.reset()
 
     if AIRFLOW_V_3_0_PLUS:
         from airflow.sdk.log import configure_logging
diff --git a/providers/celery/tests/unit/celery/cli/test_celery_command.py b/providers/celery/tests/unit/celery/cli/test_celery_command.py
index 5ccc14ffa16..a14cadfee15 100644
--- a/providers/celery/tests/unit/celery/cli/test_celery_command.py
+++ b/providers/celery/tests/unit/celery/cli/test_celery_command.py
@@ -330,17 +330,17 @@ class TestWorkerDuplicateHostnameCheck:
             cls.parser = cli_parser.get_parser()
 
     @pytest.mark.db_test
-    @mock.patch("airflow.providers.celery.executors.celery_executor.app.control.inspect")
-    def test_worker_fails_when_hostname_already_exists(self, mock_inspect):
+    @mock.patch("airflow.providers.celery.cli.celery_command.kombu.pools.reset")
+    @mock.patch("airflow.providers.celery.cli.celery_command.Celery")
+    def test_worker_fails_when_hostname_already_exists(self, mock_celery_cls, mock_pools_reset):
         """Test that worker command fails when trying to start a worker with a duplicate hostname."""
         args = self.parser.parse_args(["celery", "worker", "--celery-hostname", "existing_host"])
 
-        # Mock the inspect to return an active worker with the same hostname
-        mock_instance = MagicMock()
-        mock_instance.active_queues.return_value = {
+        mock_temp_app = MagicMock()
+        mock_temp_app.control.inspect.return_value.active_queues.return_value = {
             "celery@existing_host": [{"name": "queue1"}],
         }
-        mock_inspect.return_value = mock_instance
+        mock_celery_cls.return_value = mock_temp_app
 
         # Test that SystemExit is raised with appropriate error message
         with pytest.raises(SystemExit) as exc_info:
@@ -348,27 +348,38 @@ class TestWorkerDuplicateHostnameCheck:
 
         assert "existing_host" in str(exc_info.value)
         assert "already running" in str(exc_info.value)
+        # Pool cleanup must run even when the check raises — see issue #59707
+        mock_temp_app.close.assert_called_once()
+        mock_pools_reset.assert_called_once()
 
     @pytest.mark.db_test
-    @mock.patch("airflow.providers.celery.executors.celery_executor.app.control.inspect")
+    @mock.patch("airflow.providers.celery.cli.celery_command.kombu.pools.reset")
+    @mock.patch("airflow.providers.celery.cli.celery_command.Celery")
     @mock.patch("airflow.providers.celery.cli.celery_command.Process")
     @mock.patch("airflow.providers.celery.executors.celery_executor.app")
-    def test_worker_starts_when_hostname_is_unique(self, mock_celery_app, mock_popen, mock_inspect):
+    def test_worker_starts_when_hostname_is_unique(
+        self, mock_celery_app, mock_popen, mock_celery_cls, mock_pools_reset
+    ):
         """Test that worker command succeeds when the hostname is unique."""
         args = self.parser.parse_args(["celery", "worker", "--celery-hostname", "new_host"])
 
-        # Mock the inspect to return active workers without the new hostname
-        mock_instance = MagicMock()
-        mock_instance.active_queues.return_value = {
+        mock_temp_app = MagicMock()
+        mock_temp_app.control.inspect.return_value.active_queues.return_value = {
             "celery@existing_host": [{"name": "queue1"}],
         }
-        mock_inspect.return_value = mock_instance
+        mock_celery_cls.return_value = mock_temp_app
 
         # Worker should start successfully
         celery_command.worker(args)
 
-        # Verify that worker_main was called
+        # Verify that worker_main was called on the real app, not the temp app
         assert mock_celery_app.worker_main.called
+        mock_temp_app.worker_main.assert_not_called()
+        # Real app's inspect must not be touched — it would pollute the worker's pools
+        mock_celery_app.control.inspect.assert_not_called()
+        # Pool cleanup must still run on the happy path
+        mock_temp_app.close.assert_called_once()
+        mock_pools_reset.assert_called_once()
 
 
 @pytest.mark.backend("mysql", "postgres")