Honor execution timeout across task deferrals - #69734
Conversation
Deferred tasks could exceed their configured execution timeout because each resumed execution received the full timeout again and trigger deadlines ignored the task deadline.
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
|
|
||
| ti = session.get(TI, task_instance_id) | ||
| if ti is not None and ti.start_date is not None: | ||
| dag = dag_bag.get_dag_for_run(dag_run=ti.dag_run, session=session) |
There was a problem hiding this comment.
This deserializes the full SerializedDAG on every deferral just to read one task attribute (execution_timeout). ti_update_state with state=deferred is a hot path -- every deferrable-sensor poke and every deferrable-operator await hits it.
The sibling helper _handle_fail_fast_for_dag in this same file deliberately avoids exactly this:
# Check fail_fast from DagModel (simple column lookup) - early exit if False
# This avoids loading 5-50 MB SerializedDAG in 99% of cases
fail_fast = session.scalar(select(DagModel.fail_fast).where(DagModel.dag_id == dag_id))
if not fail_fast:
return
# Only load SerializedDAG when fail_fast=True (rare case ~1%)
ser_dag = dag_bag.get_dag_for_run(dag_run=dr, session=session)
That guard only works because fail_fast is a DagModel column. execution_timeout is task-level and lives only in the serialized blob, so there's no cheap column to gate on -- every deferral here pays the full deserialization unconditionally, even for the tasks (the majority) that set no execution_timeout.
The worker already has the value with no DB hit: _run_execute_callable reads task.execution_timeout, and the worker already ships trigger_timeout=defer.timeout in the defer payload (task_runner.py:1494 -> TIDeferredStatePayload.trigger_timeout). Compute the execution deadline (or the remaining budget) on the worker at defer time and send it in the payload the way trigger_timeout already flows; the server then does min(...) with zero deserialization.
Tradeoff: a new payload field needs an Execution API version bump (Cadwyn), while the server-side load doesn't touch the wire. But a 5-50 MB deserialization on every defer isn't worth saving a versioned field, and it regresses the fail-fast optimization this file just added. The extra session.get(TI, ...) full-ORM reload (the route only selected the columns it needed) folds into the same fix.
PR body
Deferred tasks could exceed their configured
execution_timeoutbecause every resumed execution received the full timeout again. Their deferred triggerdeadline also ignored the task execution deadline.
This change:
trigger_timeoutand the task's execution deadline.AirflowTaskTimeoutwhen the timeout elapsed while the task was deferred.Tests:
Important
🛠️ Maintainer triage note for @GautamSharma99 · by
@potiuk· 2026-07-28 16:32 UTCSome review feedback from
kaxilis waiting on you:kaxil.The ball is in your court — you've been assigned to this PR. Push a fix or reply in each thread explaining why the feedback does not apply, then mark them resolved and ping the reviewer (
kaxil) for a final look.See the Pull Request quality criteria for how to fix each item. There is no rush.
Note: your branch is 386 commits behind
main— please rebase and push again to get up-to-date CI results.Automated triage — may be imperfect; a maintainer takes the next look. We use this two-stage triage process so maintainers' limited time goes to the conversation with you.