You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2026/04/27 11:47:13 UTC
(airflow) branch main updated: run finalize when supervisor call fails (#65714)
This is an automated email from the ASF dual-hosted git repository.
potiuk 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 428f1337f1f run finalize when supervisor call fails (#65714)
428f1337f1f is described below
commit 428f1337f1ff2b8d1fd3b23f0295ea3f18ea2019
Author: Maciej Obuchowski <ob...@gmail.com>
AuthorDate: Mon Apr 27 13:47:04 2026 +0200
run finalize when supervisor call fails (#65714)
Signed-off-by: Maciej Obuchowski <ma...@datadoghq.com>
---
.../src/airflow/sdk/execution_time/task_runner.py | 12 +++++++-
.../task_sdk/execution_time/test_task_runner.py | 36 ++++++++++++++++++++++
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/task-sdk/src/airflow/sdk/execution_time/task_runner.py b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
index 6210797b558..7ec95750417 100644
--- a/task-sdk/src/airflow/sdk/execution_time/task_runner.py
+++ b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
@@ -1376,7 +1376,17 @@ def run(
Stats.incr("ti.finish", tags={**stats_tags, "state": state.value})
if msg:
- SUPERVISOR_COMMS.send(msg=msg)
+ # If the supervisor rejects the terminal-state report
+ # (e.g. the server already moved the TI to a terminal state and
+ # returns 409 - for example due to issue fixed by #65594),
+ # we still want to run finalize() so listeners observe the task state.
+ try:
+ SUPERVISOR_COMMS.send(msg=msg)
+ except Exception:
+ log.exception(
+ "Failed to report terminal task state to supervisor",
+ state=state.value,
+ )
# Return the message to make unit tests easier too
ti.state = state
diff --git a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
index 5aeb009bd33..0db47a14a3a 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
@@ -421,6 +421,42 @@ def test_main_sends_reschedule_task_when_startup_reschedules(
]
+def test_run_swallows_supervisor_terminal_send_failure(create_runtime_ti, mock_supervisor_comms):
+ """
+ When the supervisor rejects the terminal-state send (e.g. the server
+ already moved the TI to a terminal state and returns 409), `run()` must
+ not let that exception escape — the task body succeeded, so `run()` should
+ still return (SUCCESS, msg, None) so `main()` can notify listeners with
+ the correct local terminal state.
+ """
+ from airflow.sdk.execution_time.task_runner import run
+
+ class TrivialOperator(BaseOperator):
+ def execute(self, context):
+ return "ok"
+
+ task = TrivialOperator(task_id="trivial")
+ runtime_ti = create_runtime_ti(task=task)
+ context = runtime_ti.get_template_context()
+ log = mock.MagicMock()
+
+ # Let every send succeed except the terminal-state send — simulate the
+ # server rejecting the SucceedTask with a 409-wrapped AirflowRuntimeError.
+ def send_side_effect(msg=None, **kwargs):
+ if isinstance(msg, SucceedTask):
+ raise RuntimeError("409 invalid_state: previous_state=success")
+ return mock.DEFAULT
+
+ mock_supervisor_comms.send.side_effect = send_side_effect
+
+ # Must not raise.
+ state, msg, error = run(runtime_ti, context, log)
+
+ assert state == TaskInstanceState.SUCCESS
+ assert isinstance(msg, SucceedTask)
+ assert error is None
+
+
def test_task_span_is_child_of_dag_run_span(make_ti_context):
"""Full trace hierarchy: dag_run → task_run.my_task (API server) → worker.my_task (task runner)."""
# Single provider shared by all spans so contexts are compatible.