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

henry3260 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new a91a8e3d650 [v3-3-test] Stop airflow standalone leaking components 
when one fails to start (#72464) (#72568)
a91a8e3d650 is described below

commit a91a8e3d6509859e38a8253c36a236864a6fd286
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Sep 6 02:45:06 2026 +0800

    [v3-3-test] Stop airflow standalone leaking components when one fails to 
start (#72464) (#72568)
    
    * Stop airflow standalone leaking components when one fails to start
    
    SubCommand.process is only assigned once the thread reaches Popen, so a
    component whose executable could not be spawned leaves the attribute absent
    rather than empty. Shutdown then raises out of the loop that terminates each
    component in turn, and every sibling after the failed one keeps running
    against the metadata database after standalone itself has exited.
    
    * Update airflow-core/tests/unit/cli/commands/test_standalone_command.py
    
    ---------
    (cherry picked from commit 841288926725a61919fb96b92a648929081ef0e4)
    
    Co-authored-by: Y-C <[email protected]>
    Co-authored-by: Eason09053360 
<[email protected]>
    Co-authored-by: Henry Chen <[email protected]>
---
 airflow-core/src/airflow/cli/commands/standalone_command.py |  4 +++-
 .../tests/unit/cli/commands/test_standalone_command.py      | 13 +++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/cli/commands/standalone_command.py 
b/airflow-core/src/airflow/cli/commands/standalone_command.py
index 2e94637e763..97b327b426b 100644
--- a/airflow-core/src/airflow/cli/commands/standalone_command.py
+++ b/airflow-core/src/airflow/cli/commands/standalone_command.py
@@ -291,6 +291,7 @@ class SubCommand(threading.Thread):
         self.name = name
         self.command = command
         self.env = env
+        self.process: subprocess.Popen[bytes] | None = None
 
     def run(self):
         """Run the actual process and captures it output to a queue."""
@@ -305,7 +306,8 @@ class SubCommand(threading.Thread):
 
     def stop(self):
         """Call to stop this process (and thus this thread)."""
-        self.process.terminate()
+        if self.process is not None:
+            self.process.terminate()
 
 
 # Alias for use in the CLI parser
diff --git a/airflow-core/tests/unit/cli/commands/test_standalone_command.py 
b/airflow-core/tests/unit/cli/commands/test_standalone_command.py
index 993f51ca2d7..b2e7c2496fa 100644
--- a/airflow-core/tests/unit/cli/commands/test_standalone_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_standalone_command.py
@@ -18,6 +18,7 @@
 from __future__ import annotations
 
 import os
+import subprocess
 from collections import deque
 from importlib import reload
 from unittest import mock
@@ -304,6 +305,18 @@ class TestStandaloneCommand:
 
         fake_process.terminate.assert_called_once()
 
+    def 
test_subcommand_stop_does_not_block_siblings_when_a_process_never_started(self):
+        """Shutdown stops each component in turn, so one that never spawned 
must not abort the loop."""
+        never_started = SubCommand(mock.Mock(spec=StandaloneCommand), 
"scheduler", ["scheduler"], {})
+        running = SubCommand(mock.Mock(spec=StandaloneCommand), "triggerer", 
["triggerer"], {})
+        running.process = mock.Mock(spec=subprocess.Popen)
+
+        assert never_started.process is None
+        for command in (never_started, running):
+            command.stop()
+
+        running.process.terminate.assert_called_once()
+
     
@mock.patch("airflow.cli.commands.standalone_command.ExecutorLoader.import_default_executor_cls")
     @mock.patch("airflow.cli.commands.standalone_command.conf.get")
     @mock.patch.dict(os.environ, {}, clear=True)

Reply via email to