kaxil opened a new pull request, #74719:
URL: https://github.com/apache/airflow/pull/74719

   Closes #74660
   
   A retry policy can decide RETRY, FAIL or DEFAULT, but some failures mean 
"not yet" rather than "this went wrong". A KubernetesPodOperator task whose 
namespace `ResourceQuota` is full gets a 403 on pod creation; at the start of 
the day that can last hours. Today the choice is to subclass the operator and 
raise `AirflowRescheduleException`, or to set dozens of retries, which then 
fire `on_retry_callback` every few minutes and leave no retries for a real 
failure once capacity frees up.
   
   This adds `RetryAction.RESCHEDULE`. The task goes to `up_for_reschedule`, 
the state a sensor in reschedule mode already uses, so the try number and 
`retries` are untouched and `on_retry_callback` does not fire. It works with 
`retries=0`.
   
   ```python
   RetryRule(
       exception=WarehouseBusyError,
       action=RetryAction.RESCHEDULE,
       retry_delay=timedelta(minutes=10),
       reschedule_timeout=timedelta(hours=4),  # and/or max_reschedules=24
       reason="Warehouse busy",
   )
   
   # or from a custom policy
   RetryDecision.reschedule(timedelta(minutes=10), 
reschedule_timeout=timedelta(hours=4), reason="Namespace quota full")
   ```
   
   ## Design rationale
   
   **No Execution API or database change.** Any task can already move to 
`up_for_reschedule`: the worker sends the same `RescheduleTask` message a 
sensor does, and the server and scheduler paths are unchanged. The policy's 
reason is logged rather than stored; storing it would need a new field on the 
reschedule payload.
   
   **Every RESCHEDULE is bounded.** A rule matching a broad exception would 
otherwise turn every real failure into an endless loop that never touches 
`retries`. `max_reschedules` and `reschedule_timeout` live on `RetryDecision` 
and `RetryRule`, a RESCHEDULE without either raises `ValueError` when it is 
created (so a bad rule fails at parse time), and the task runner enforces them 
rather than trusting each policy to. Past a bound, the failure goes through the 
standard retry logic with a `retry_reason` such as `Reschedule limit reached 
(reschedule_timeout=4:00:00): Warehouse busy`. Setting either bound on another 
action also raises.
   
   **Bounds count the try's reschedules, the records sensors already use.** 
`task_reschedule_count` and the first reschedule date reset when a new try 
starts, so a retry gets a fresh allowance and `retries` still caps the total.
   
   **This is for an external system the task drives, not for disruption of the 
Airflow worker.** The policy runs in the worker and only sees the exception the 
task raised. A preempted worker pod is the executor's business (the subject of 
the AIP-97 classification and AIP-122 infrastructure retry proposals), and a 
policy never sees it.
   
   **Composing policies keeps the bounds.** `ChainRetryPolicy`, its 
`common.compat` backport and `ClassifierRetryPolicy`'s fallback paths rebuilt 
the winning decision field by field, which would drop the new fields and turn a 
valid RESCHEDULE into a `ValueError`. They now use `dataclasses.replace`.
   
   **A rescheduled failure still fails closed.** If the terminal-state message 
cannot reach the supervisor, a task whose exception was turned into a 
reschedule now exits non-zero like FAILED and UP_FOR_RETRY do, so it is not 
reported as SUCCESS. A sensor's own reschedule is unchanged.
   
   Run end to end with LocalExecutor and a task that always raises 
(`max_reschedules=2`, `retries=1`, `retry_delay=5s`, 10s reschedule delay): try 
1 ran three times (two reschedules, then the bound), retried after the task's 
own 5s delay, try 2 ran three times the same way and then failed. 
`task_reschedule` held two rows per try, and both tries stored `retry_reason = 
Reschedule limit reached (max_reschedules=2): cluster full`.
   
   ## Known issues
   
   - Reschedules from any cause share the try's counter: a sensor's pokes and 
the worker's own reschedule of a task whose Dag is not yet visible both count 
toward the bounds, and policy reschedules use up `[workers] 
missing_dag_retries`. Sensors in reschedule mode already have this interaction. 
Telling them apart needs a source column on `task_reschedule`.
   - The OpenLineage listener only skips a repeated START for operators with 
`reschedule=True`, so a policy-driven reschedule emits another START for the 
same run.
   - Released `apache-airflow-providers-common-ai` versions rebuild decisions 
field by field, so a RESCHEDULE from `ClassifierRetryPolicy`'s 
`fallback_policy` or `fallback_rules` falls back to DEFAULT until the provider 
ships this change.
   


-- 
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