Skip to content

Fix scheduler firing on_failure_callback for heartbeat-timed-out retries - #66767

Merged
vatsrahul1001 merged 1 commit into
apache:mainfrom
1fanwang:fix/65400-heartbeat-callback-type
Jul 13, 2026
Merged

Fix scheduler firing on_failure_callback for heartbeat-timed-out retries#66767
vatsrahul1001 merged 1 commit into
apache:mainfrom
1fanwang:fix/65400-heartbeat-callback-type

Conversation

@1fanwang

@1fanwang1fanwang commented May 12, 2026

Copy link
Copy Markdown
Contributor

When a worker stops heartbeating mid-retry (OOM kill, eviction, node drain), the scheduler's heartbeat-timeout cleanup enqueued a callback request without marking it as a retry, so the Dag processor fired on_failure_callback even when the task still had retries left — producing spurious failure alerts for tasks that ultimately succeeded on retry.

closes: #65400

What

The cleanup now marks the callback as a retry when the task is still retry-eligible, so on_retry_callback fires instead of on_failure_callback for heartbeat-timed-out retries.

Tests

Parametrized regression test_heartbeat_timeout_sets_callback_type_by_retry_eligibility drives the real purge path and asserts both the callback type and the dispatched callback across the full retry matrix (no retries, retries available, mid-chain, exhausted). Each case fails on main and passes here.

Picking up from #65404 by @kimhaggie (closed for inactivity).

@1fanwang
1fanwang requested review from XD-DENG and ashb as code ownersMay 12, 2026 13:25
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label May 12, 2026
@kimhaggie

kimhaggie commented May 13, 2026

Copy link
Copy Markdown
Contributor

Hi @1fanwang, thanks for picking this up. Since this builds directly on #65404 (the diff is essentially identical), could you add this trailer to the commit message?

Co-authored-by: kimhaggie <kimhaggie@gmail.com>

@potiuk, could you make sure the trailer is preserved when this is squash-merged? Appreciate it.

@1fanwang
1fanwangforce-pushed the fix/65400-heartbeat-callback-type branch from 4656c17 to 608231dCompareMay 13, 2026 06:32
@1fanwang

1fanwang commented May 13, 2026

Copy link
Copy Markdown
ContributorAuthor

Hi @1fanwang, thanks for picking this up. Since this builds directly on #65404 (the diff is essentially identical), could you add this trailer to the commit message?

Co-authored-by: kimhaggie <kimhaggie@gmail.com>

@potiuk, could you make sure the trailer is preserved when this is squash-merged? Appreciate it.

Added — squashed + force-pushed with the trailer.

Separately: open to closing this in favor of #65404 if you'd like to take it forward yourself. You opened first, you're the original reporter, and your version was in good shape — the credit should land with you. Let me know if you'd like to reopen #65404 and I'll close this one out.

@kimhaggie

Copy link
Copy Markdown
Contributor

Hi @1fanwang, thanks for picking this up. Since this builds directly on #65404 (the diff is essentially identical), could you add this trailer to the commit message?

Co-authored-by: kimhaggie <kimhaggie@gmail.com>

@potiuk, could you make sure the trailer is preserved when this is squash-merged? Appreciate it.

Added — squashed + force-pushed with the trailer.

Separately: open to closing this in favor of #65404 if you'd like to take it forward yourself. You opened first, you're the original reporter, and your version was in good shape — the credit should land with you. Let me know if you'd like to reopen #65404 and I'll close this one out.

Thanks @1fanwang, really appreciate both the trailer and the offer. I'll let this PR proceed since you've already put in the work. Happy with co-author attribution — looking forward to seeing this merged.

@choo121600choo121600 added the ready for maintainer review Set after triaging when all criteria pass. label May 15, 2026
@1fanwang
1fanwangforce-pushed the fix/65400-heartbeat-callback-type branch 2 times, most recently from 2f039f2 to b760421CompareJune 19, 2026 18:42
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
@1fanwang
1fanwangforce-pushed the fix/65400-heartbeat-callback-type branch from b760421 to 70eef3cCompareJune 29, 2026 06:50
When a worker stops heartbeating (OOMKill, node eviction), the scheduler's
``_purge_task_instances_without_heartbeats`` built a ``TaskCallbackRequest``
without ``task_callback_type``. The Dag processor's task-callback dispatch
branches on that field: ``UP_FOR_RETRY`` runs ``on_retry_callback``, anything
else (including ``None``) runs ``on_failure_callback``. So heartbeat-timeout
cleanup always fired ``on_failure_callback`` even when the task still had
retries remaining, producing spurious failure alerts for tasks that
ultimately succeeded on retry.
Set ``task_callback_type`` from ``ti.is_eligible_to_retry()``, the canonical
retry-eligibility predicate, guarded by ``max_tries > 0``. The guard covers
the one gap the predicate has here: this path doesn't load ``ti.task``, so the
predicate falls back to ``try_number <= max_tries`` and drops the
retries-configured check its task-loaded branch applies. Deferring to the
predicate also keeps a ``RESTARTING`` task (cleared while running) retry-
eligible past ``max_tries``, where a hand-rolled ``try_number <= max_tries``
check would have fired ``on_failure_callback``.
closes: apache#65400
Co-authored-by: kimhaggie <kimhaggie@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwangforce-pushed the fix/65400-heartbeat-callback-type branch from 70eef3c to bd1f186CompareJune 29, 2026 16:31

@amoghrajeshamoghrajesh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

@amoghrajeshamoghrajesh added this to the Airflow 3.3.1 milestone Jul 13, 2026
@eladkaleladkal added type:bug-fix Changelog: Bug Fixes backport-to-v3-3-test Backport to v3-3-test labels Jul 13, 2026
@vatsrahul1001
vatsrahul1001 merged commit f2403cc into apache:mainJul 13, 2026
77 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-3-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-3-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Jul 13, 2026
…med-out retries (apache#66767)
When a worker stops heartbeating (OOMKill, node eviction), the scheduler's
``_purge_task_instances_without_heartbeats`` built a ``TaskCallbackRequest``
without ``task_callback_type``. The Dag processor's task-callback dispatch
branches on that field: ``UP_FOR_RETRY`` runs ``on_retry_callback``, anything
else (including ``None``) runs ``on_failure_callback``. So heartbeat-timeout
cleanup always fired ``on_failure_callback`` even when the task still had
retries remaining, producing spurious failure alerts for tasks that
ultimately succeeded on retry.
Set ``task_callback_type`` from ``ti.is_eligible_to_retry()``, the canonical
retry-eligibility predicate, guarded by ``max_tries > 0``. The guard covers
the one gap the predicate has here: this path doesn't load ``ti.task``, so the
predicate falls back to ``try_number <= max_tries`` and drops the
retries-configured check its task-loaded branch applies. Deferring to the
predicate also keeps a ``RESTARTING`` task (cleared while running) retry-
eligible past ``max_tries``, where a hand-rolled ``try_number <= max_tries``
check would have fired ``on_failure_callback``.
closes: apache#65400
(cherry picked from commit f2403cc)
Co-authored-by: Stefan Wang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Co-authored-by: kimhaggie <kimhaggie@gmail.com>
eladkal pushed a commit that referenced this pull request Jul 13, 2026
…med-out retries (#66767) (#69824)
When a worker stops heartbeating (OOMKill, node eviction), the scheduler's
``_purge_task_instances_without_heartbeats`` built a ``TaskCallbackRequest``
without ``task_callback_type``. The Dag processor's task-callback dispatch
branches on that field: ``UP_FOR_RETRY`` runs ``on_retry_callback``, anything
else (including ``None``) runs ``on_failure_callback``. So heartbeat-timeout
cleanup always fired ``on_failure_callback`` even when the task still had
retries remaining, producing spurious failure alerts for tasks that
ultimately succeeded on retry.
Set ``task_callback_type`` from ``ti.is_eligible_to_retry()``, the canonical
retry-eligibility predicate, guarded by ``max_tries > 0``. The guard covers
the one gap the predicate has here: this path doesn't load ``ti.task``, so the
predicate falls back to ``try_number <= max_tries`` and drops the
retries-configured check its task-loaded branch applies. Deferring to the
predicate also keeps a ``RESTARTING`` task (cleared while running) retry-
eligible past ``max_tries``, where a hand-rolled ``try_number <= max_tries``
check would have fired ``on_failure_callback``.
closes: #65400
(cherry picked from commit f2403cc)
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Co-authored-by: Stefan Wang <1fannnw@gmail.com>
Co-authored-by: kimhaggie <kimhaggie@gmail.com>
joshuabvarghese pushed a commit to joshuabvarghese/airflow that referenced this pull request Jul 16, 2026
…ies (apache#66767)
When a worker stops heartbeating (OOMKill, node eviction), the scheduler's
``_purge_task_instances_without_heartbeats`` built a ``TaskCallbackRequest``
without ``task_callback_type``. The Dag processor's task-callback dispatch
branches on that field: ``UP_FOR_RETRY`` runs ``on_retry_callback``, anything
else (including ``None``) runs ``on_failure_callback``. So heartbeat-timeout
cleanup always fired ``on_failure_callback`` even when the task still had
retries remaining, producing spurious failure alerts for tasks that
ultimately succeeded on retry.
Set ``task_callback_type`` from ``ti.is_eligible_to_retry()``, the canonical
retry-eligibility predicate, guarded by ``max_tries > 0``. The guard covers
the one gap the predicate has here: this path doesn't load ``ti.task``, so the
predicate falls back to ``try_number <= max_tries`` and drops the
retries-configured check its task-loaded branch applies. Deferring to the
predicate also keeps a ``RESTARTING`` task (cleared while running) retry-
eligible past ``max_tries``, where a hand-rolled ``try_number <= max_tries``
check would have fired ``on_failure_callback``.
closes: apache#65400
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Co-authored-by: kimhaggie <kimhaggie@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Aug 5, 2026
…med-out retries (#66767) (#69824)
When a worker stops heartbeating (OOMKill, node eviction), the scheduler's
``_purge_task_instances_without_heartbeats`` built a ``TaskCallbackRequest``
without ``task_callback_type``. The Dag processor's task-callback dispatch
branches on that field: ``UP_FOR_RETRY`` runs ``on_retry_callback``, anything
else (including ``None``) runs ``on_failure_callback``. So heartbeat-timeout
cleanup always fired ``on_failure_callback`` even when the task still had
retries remaining, producing spurious failure alerts for tasks that
ultimately succeeded on retry.
Set ``task_callback_type`` from ``ti.is_eligible_to_retry()``, the canonical
retry-eligibility predicate, guarded by ``max_tries > 0``. The guard covers
the one gap the predicate has here: this path doesn't load ``ti.task``, so the
predicate falls back to ``try_number <= max_tries`` and drops the
retries-configured check its task-loaded branch applies. Deferring to the
predicate also keeps a ``RESTARTING`` task (cleared while running) retry-
eligible past ``max_tries``, where a hand-rolled ``try_number <= max_tries``
check would have fired ``on_failure_callback``.
closes: #65400
(cherry picked from commit f2403cc)
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Co-authored-by: Stefan Wang <1fannnw@gmail.com>
Co-authored-by: kimhaggie <kimhaggie@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerbackport-to-v3-3-testBackport to v3-3-testready for maintainer reviewSet after triaging when all criteria pass.type:bug-fixChangelog: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scheduler heartbeat-timeout cleanup creates TaskCallbackRequest without task_callback_type, causing on_failure_callback to fire while retries remain

8 participants

@1fanwang@kimhaggie@uranusjr@amoghrajesh@vatsrahul1001@eladkal@jason810496@choo121600