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)