AIP-97: Disruption Readiness (Transparent Infra Failure Retries through Context Capture and Propagation) - #66405
Open
1fanwang wants to merge 7 commits into
Open
AIP-97: Disruption Readiness (Transparent Infra Failure Retries through Context Capture and Propagation)#664051fanwang wants to merge 7 commits into
1fanwang wants to merge 7 commits into
Conversation
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
May 5, 2026 10:21
20d6bd9 to
8c53810
Compare
This was referenced May 5, 2026
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 16, 2026 05:33
57f338e to
fcc393a
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:28
fcc393a to
bd8e7ec
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:38
bd8e7ec to
e87f0ba
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 22:39
e87f0ba to
7d568c9
Compare
This was referenced Jul 24, 2026
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
2 times, most recently
from
July 27, 2026 19:25
ab413e1 to
c822f45
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
2 times, most recently
from
July 28, 2026 01:13
aa82a98 to
0de4133
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 28, 2026 07:45
0de4133 to
4a545fd
Compare
1fanwang
marked this pull request as ready for review
July 29, 2026 08:28
1fanwang
requested review from
XD-DENG,
amoghrajesh,
ashb,
bugraoz93,
choo121600,
dheerajturaga,
ephraimbuddy,
henry3260,
hussein-awala,
jason810496,
jedcunningham,
jscheffl,
kaxil,
o-nikolas,
pierrejeambrun,
potiuk,
rawwar and
shubhamraj-git
as code owners
July 29, 2026 08:28
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>
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 30, 2026 04:19
47a4827 to
8d4d715
Compare
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>
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 free
to 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.
The implementation behind AIP-97: Disruption Readiness.
Why
When a task is killed from outside (eviction, OOM, force-delete, lost heartbeat), the worker dies without raising. An eviction and a real
ValueErrortherefore look the same toon_task_instance_failed, to lineage, and to the retry counter. Airflow already requeues a pre-start pod without spending a retry; this extends that behavior to running tasks.What
Pillar 1 adds an optional
failure_kindenum (infra,application,timeout,manual) and a shortreasontoken to the hook. Pluggy matches by name, so listeners that do not declare the new arguments are unchanged. The Kubernetes executor'sclassify_pod_failure()reads pod details it already collects, with its reason set pinned to the kubelet's Go definitions. Node drains and preemptions leave that reason empty, so it also readspod.status.conditions[type=DisruptionTarget].Pillar 2 is opt-in and off by default: on an
infrafailure, refund the attempt instead of charging the user'sretries, reusingmax_triesand capping atmax_infra_refunds.Retry eligibility
The refund lifts
max_tries, sois_eligible_to_retrynow readsmax_triesrather than the user'sretries. That matches the Execution API's_is_eligible_to_retry, which already droppedretries, so the two retry paths stop disagreeing and aretries=0task gets the refund. Eligibility differs only once a refund liftsmax_triespastretries.Alternatives set aside: an
infra_retry_countcolumn would bring back the migration this avoids; ahandle_failurebypass leaves the eligibility paths divergent; checkpoint-resume needs AIP-103 state this design does not keep.No DB change
The reason reaches the listener as a transient argument, not a column, so there is no migration and it backports cleanly. Whether it rides
reasonor folds intoerroris open (#23 vs #22).Related POCs
failure_kindalso reaches callbacks, metrics (live OTel e2e), and scheduler logs. Other executor paths are POC'd for Celery and heartbeat, with config and listener docs.Testing
Unit: AIP-97 tests pass across core, Kubernetes, the scheduler, and the task SDK; existing scheduler retry-eligibility tests are unchanged;
mypyis clean.Live on real Postgres 16, through the real (not mocked)
handle_failure(failure_kind=...)path on3ce5dbc184; core and task SDK runtime are unchanged since. A registered listener confirms it receives kind and reason.airflow db migratestamps to the existing head:Commands and raw logs
Kubernetes, live on a v1.35.0 cluster. A node drain and a preemption are indistinguishable from a crash except for the condition:
Commands and raw logs
Both providers still import on Airflow 3.3.0;
classify_pod_failureis gated to 3.4+.