You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ds...@apache.org on 2025/04/17 18:56:18 UTC

(airflow) branch main updated: Don't update start_date when resuming a TI from deferral (#49407)

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

dstandish 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 60d4df1b894 Don't update start_date when resuming a TI from deferral (#49407)
60d4df1b894 is described below

commit 60d4df1b8942813ce47bb60d411026da52ed04ce
Author: Daniel Standish <15...@users.noreply.github.com>
AuthorDate: Thu Apr 17 11:56:12 2025 -0700

    Don't update start_date when resuming a TI from deferral (#49407)
    
    This allows duration to reflect the overall time from task start to task completion, which is how the metric was calculated in 2.x.
---
 .../execution_api/routes/task_instances.py         |  4 ++
 .../versions/head/test_task_instances.py           | 61 ++++++++++++++++++++++
 2 files changed, 65 insertions(+)

diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
index af2bf86c7cd..ca8058bfd3a 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
@@ -131,6 +131,10 @@ def ti_run(
     # We exclude_unset to avoid updating fields that are not set in the payload
     data = ti_run_payload.model_dump(exclude_unset=True)
 
+    # don't update start date when resuming from deferral
+    if ti.next_kwargs:
+        data.pop("start_date")
+
     query = update(TI).where(TI.id == ti_id_str).values(data)
 
     previous_state = ti.state
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
index 4d36a2f5515..49d4f42b9a6 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
@@ -268,6 +268,67 @@ class TestTIRunState:
             },
         }
 
+    @pytest.mark.parametrize("resume", [True, False])
+    def test_next_kwargs_determines_start_date_update(self, client, session, create_task_instance, resume):
+        dag_start_time_str = "2024-09-30T12:00:00Z"
+        dag_start_time = timezone.parse(dag_start_time_str)
+        orig_task_start_time = dag_start_time.add(seconds=5)
+
+        ti = create_task_instance(
+            task_id="test_next_kwargs_still_encoded",
+            state=State.QUEUED,
+            session=session,
+            start_date=orig_task_start_time,
+        )
+
+        ti.start_date = orig_task_start_time
+        ti.next_method = "execute_complete"
+
+        second_start_time = orig_task_start_time.add(seconds=30)
+        second_start_time_str = second_start_time.isoformat()
+
+        # ti.next_kwargs under the hood applies the serde encoding for us
+        if resume:
+            ti.next_kwargs = {"moment": second_start_time}
+            expected_start_date = orig_task_start_time
+            expected_next_kwargs = {
+                "__type": "dict",
+                "__var": {"moment": {"__type": "datetime", "__var": second_start_time.timestamp()}},
+            }
+        else:
+            expected_start_date = second_start_time
+            expected_next_kwargs = None
+
+        session.commit()
+
+        response = client.patch(
+            f"/execution/task-instances/{ti.id}/run",
+            json={
+                "state": "running",
+                "hostname": "random-hostname",
+                "unixname": "random-unixname",
+                "pid": 100,
+                "start_date": second_start_time_str,
+            },
+        )
+        session.commit()
+
+        assert response.status_code == 200
+        assert response.json() == {
+            "dag_run": mock.ANY,
+            "task_reschedule_count": 0,
+            "max_tries": 0,
+            "should_retry": False,
+            "variables": [],
+            "connections": [],
+            "xcom_keys_to_clear": [],
+            "next_method": "execute_complete",
+            "next_kwargs": expected_next_kwargs,
+        }
+        session.expunge_all()
+        ti = session.get(TaskInstance, ti.id)
+        assert ti.start_date == expected_start_date
+
     @pytest.mark.parametrize(
         "initial_ti_state",
         [s for s in TaskInstanceState if s not in (TaskInstanceState.QUEUED, TaskInstanceState.RESTARTING)],