Uh oh!
There was an error while loading. Please reload this page.
Add failure causes and infrastructure retry budgets - #66405
Draft
1fanwang wants to merge 10 commits into
Draft
Conversation
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
May 5, 2026 10:21
20d6bd9 to
8c53810Compare
This was referenced May 5, 2026
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 16, 2026 05:33
57f338e to
fcc393aCompare1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:28
fcc393a to
bd8e7ecCompare1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:38
bd8e7ec to
e87f0baCompare1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 22:39
e87f0ba to
7d568c9Compare
This was referenced Jul 24, 2026
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
2 times, most recently
from
July 27, 2026 19:25
ab413e1 to
c822f45Compare1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
2 times, most recently
from
July 28, 2026 01:13
aa82a98 to
0de4133Compare1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 28, 2026 07:45
0de4133 to
4a545fdCompare1fanwang
requested review from
XD-DENG, choo121600, dheerajturaga, henry3260 and o-nikolas
as code ownersJuly 29, 2026 08:29
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
July 30, 2026 04:19
47a4827 to
8d4d715Compare
This was referenced Aug 9, 2026
potiuk
marked this pull request as draft
August 13, 2026 12:55
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
August 25, 2026 04:29
baeab77 to
eb329f4Compare
This was referenced Aug 25, 2026
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
2 times, most recently
from
August 25, 2026 15:22
97fefd5 to
6abd295Compare…nfra retries (no DB) Pillar 1: add a `failure_kind` argument (infra / application / timeout / manual) and a transient `reason` token to the `on_task_instance_failed` listener hook. The worker sets application or timeout for the failures it catches; the scheduler sets infra for a task killed from outside. The Kubernetes executor classifies a failed pod (Evicted / preempted is infra; an app crash or an OOMKill against the container's own limit is application). pluggy dispatches by name, so a listener that does not declare the arguments is unchanged. Pillar 2: a failure classified as infra gives back the retry it used (reuses `max_tries`, bounded by `[core] max_infra_refunds`), so infra churn does not spend a task's `retries`. Opt-in via `[core] infra_failure_refund_retries`, off by default. This holds even at `retries=0`: `is_eligible_to_retry` now gates on `max_tries` (the retry ceiling the refund bumps), matching the execution API's `_is_eligible_to_retry`, rather than short-circuiting on the user's `retries`. The two retry-decision paths no longer disagree, and no per-task counter or column is needed. No schema change: the reason is handed to the listener at failure time rather than persisted, so this backports cleanly to older release branches with no upgrade or rollback step. How the reason token best reaches the listener (this transient `reason` argument, or folding it into the existing `error` argument) is AIP-97 open question 3. Signed-off-by: 1fanwang <1fannnw@gmail.com>
finalize() omitted reason= on both on_task_instance_failed calls. pluggy raises HookCallError when a hookspec argument is missing, and the call is wrapped in a bare except, so a listener using the signature the hookspec documents silently never fired on the worker path. Also trims narrating comments and docstrings down to the reasons the code can't carry, and corrects handle_failure's docstring, which said reason reaches the listener as error. Signed-off-by: 1fanwang <1fannnw@gmail.com>
Four of the reasons could never match. TerminationByKubelet, DeletionByTaintManager and DisruptionTarget are pod *condition* reasons, and Shutdown is a node event reason; the classifier reads only pod.status.reason and a container's terminated/waiting reason, so none of them was reachable. The tests asserted the same wrong assumption, so they passed. Replace them with Terminated, which graceful node shutdown writes to pod.status.reason when it kills an already-running pod. That is the node-drain case this feature exists for, and it was missing. The kubelet is its sole writer, so it cannot collide with an application exit, and it never appears as a container reason. Pin every remaining reason to its Go definition, and record that a taint-manager eviction and a scheduler preemption stay unclassified until the classifier reads pod.status.conditions. Also type the signatures this change added and keyword the watcher call it modified. Signed-off-by: 1fanwang <1fannnw@gmail.com>
The stub classes and helpers this PR adds were half-typed: one _TI stub was annotated while its sibling was not, and _eligible/_run left one parameter bare. Types the remaining ones to their real domain types (_run's stashed is the executor's failure-info tuple, not a flag). Signed-off-by: 1fanwang <1fannnw@gmail.com>
The comment editorialised about a change that now has its own PR. Keep only what stops the footgun it warned about: condition reasons are not matched against pod.status.reason, so they do not belong in this set. Signed-off-by: 1fanwang <1fannnw@gmail.com>
A node drain and a scheduler preemption are infrastructure taking the pod away, but Airflow reported both to the listener as an application failure and refunded nothing. Verified live on a k8s v1.35.0 cluster: both reach phase=Failed with pod.status.reason empty and the container reading only Error/exit 143, so neither the pod reason nor the container reason can see them. The signal lives in pod.status.conditions[type=DisruptionTarget], which the control plane sets before the delete and which survives onto the terminal object. Read that condition in collect_pod_failure_details and check it first in classify_pod_failure. Gated on status "True", matching Kubernetes' own podFailurePolicy matcher, since the writers update the condition in place. Signed-off-by: 1fanwang <1fannnw@gmail.com>
Tasks should not lose retries meant for their own failures when a backend can prove that infrastructure stopped the worker. A separate durable budget also keeps the cap stable across scheduler restarts and task clears. Signed-off-by: 1fanwang <1fannnw@gmail.com>
The integration path should prove that one executor event drives retry accounting, listeners, callback context, and persisted state consistently for classified and ambiguous failures. Signed-off-by: 1fanwang <1fannnw@gmail.com>
The new cause fields reached runtime consumers, but the supervisor snapshot and response expectations still described the old payloads. The hybrid executor forwarding also targeted a runtime removed before this contract exists. Signed-off-by: 1fanwang <1fannnw@gmail.com>
PythonVirtualenvOperator serializes only the context keys it declares, so `failure_kind` and `failure_reason` were reaching a normal task but being dropped inside a virtual environment. The guard test that compares the real context against the declared set caught the gap on four operators. They are gated on Airflow 3.4 because that is the release that introduces them, and the standard provider still runs against 3.0 through 3.3, where the context has no such keys and an unconditional entry would break the same test in the other direction. This mirrors how `partition_key` is gated on 3.3. The TypeScript supervisor schema is generated from the same wire model, so it is regenerated here to match. Signed-off-by: 1fanwang <1fannnw@gmail.com>
1fanwangforce-pushed
the
1fanwang/aip97-failure-details
branch
from
August 29, 2026 14:28
e31e2b2 to
6f11163Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Airflow can record that a task failed without knowing that the platform removed its worker. The failure then looks like task code and consumes the task's normal retries.
This adds a typed cause contract to workers, the API, and
BaseExecutor. Confirmed infrastructure failures can use a separateinfra_retriesbudget; the default is0, so upgrades do not add attempts. AIP-103 stores the count across scheduler restarts and task clears without a new migration. Ambiguous signals stay unclassified and keep today's behavior.AIP: https://cwiki.apache.org/confluence/x/0ITMFw
Executor proofs: 1fanwang#28 (ECS Spot), 1fanwang#15 (Celery reason-only). Stateless fallback: 1fanwang#16.
End-to-end evidence
An external kill of a
retries=0task, driven through the real executor and scheduler path:FAILEDinfrawith reasonEvictedUP_FOR_RETRY, infrastructure retry 1/1applicationwith reasonOOMKilledFAILEDWorkerLostwith no kindFAILEDCommands and raw output
Was generative AI tooling used to co-author this PR?
Generated-by: GitHub Copilot CLI 1.0.80 (GPT-5.6 Sol) following the guidelines