abhijeets25012-tech commented on code in PR #76669:
URL: https://github.com/apache/airflow/pull/76669#discussion_r4511865337


##########
airflow-core/tests/unit/models/test_dagrun.py:
##########
@@ -2873,6 +2873,72 @@ def 
test_verifying_removed_map_index_does_not_allocate_attempt(dag_maker, sessio
     )
 
 
[email protected]("airflow._shared.observability.metrics.stats._get_backend")
+def 
test_restoring_shrink_removed_map_index_without_resolved_length_adds_no_attempt(
+    mock_get_backend, dag_maker, session
+):
+    """Restoring an index before the map length is known must not record a Dag 
re-add or a try."""
+    mock_stats = mock.MagicMock(spec=StatsLogger)
+    mock_get_backend.return_value = mock_stats
+
+    with dag_maker(session=session) as dag:
+
+        @task
+        def producer(): ...
+
+        @task
+        def consumer(arg): ...
+
+        consumer.expand(arg=producer())
+
+    dr = dag_maker.create_dagrun()
+    producer_ti = dr.get_task_instance("producer", session=session)
+    producer_ti.state = TaskInstanceState.SUCCESS
+    push_mapped_length(producer_ti, [1, 2, 3], session=session)
+    session.flush()
+    dr.task_instance_scheduling_decisions(session=session)
+
+    # A producer re-run returns a shorter list, so indexes 1 and 2 are removed 
at try 1.
+    push_mapped_length(producer_ti, [1], session=session)
+    dag_version_id = DagVersion.get_latest_version(dag.dag_id, 
session=session).id
+    dr.verify_integrity(dag_version_id=dag_version_id, session=session)
+    removed = dr.get_task_instance("consumer", map_index=1, session=session)
+    assert removed.state == TaskInstanceState.REMOVED
+    removed_id = removed.id
+    removed_try_number = removed.try_number
+
+    # Clearing only the producer puts the removed indexes back before the new 
list is known.
+    producer_ti = producer_ti.prepare_db_for_next_try(session)
+    producer_ti.state = None
+    session.flush()
+    dr.verify_integrity(dag_version_id=dag_version_id, session=session)
+
+    restored = dr.get_task_instance("consumer", map_index=1, session=session)
+    assert restored.state is None
+    assert restored.id == removed_id
+    assert restored.try_number == removed_try_number

Review Comment:
   Good point. The existing test only covers try 0, which doesn't reproduce the 
reported issue. I've updated the regression test to schedule or execute map 
index 1 before shrinking the mapping, so it covers restoration after try 1 and 
verifies that the previous attempt isn't reused.



##########
airflow-core/src/airflow/models/dagrun.py:
##########
@@ -2411,6 +2412,14 @@ def _check_for_removed_or_restored_tasks(
                     continue
 
             if ti.state == TaskInstanceState.REMOVED:
+                if mapping_unresolved:
+                    # The instance may have been removed because a producer 
shrank the list, not
+                    # because the task left the Dag, and the resolved length 
is still unknown. Put
+                    # it back without counting a Dag re-add or allocating an 
attempt that may never
+                    # run; _revise_map_indexes_if_mapped removes it again if 
the list stays short.
+                    self.log.debug("Restoring mapped instance '%s' while its 
map length is unresolved", ti)
+                    ti.state = None

Review Comment:
   Thanks for catching this. You're right that restoring an already-executed 
attempt without preparing its successor can leave stale attempt metadata on the 
row. I've updated the logic to distinguish attempts that have actually started 
from those that have never started, so previously executed attempts follow the 
normal next-attempt preparation path.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to