miquelmartorell opened a new issue, #72716:
URL: https://github.com/apache/airflow/issues/72716

   ### Under which category would you file this issue?
   
   Airflow Core
   
   ### Apache Airflow version
   
   3.3.1 (checked against `main`, unchanged)
   
   ### What happened and how to reproduce it?
   
   Another variant of the same family, on the defer path: the resumed attempt 
is already
   `RUNNING` when the stale executor event is drained.
   
   A deferrable sensor defers. The worker process exits cleanly on `defer()`, 
and the
   executor reports `SUCCESS` for that try. The trigger fires, the scheduler 
requeues the
   task instance, and the worker starts the resumed attempt — the TI reaches 
`RUNNING` and
   clears `next_method`. Only *then* does the scheduler drain the stale 
executor success
   event from the defer-exit. In `process_executor_events`, `ti_queued` is True 
(`RUNNING`
   is in the tuple) and `ti_requeued` is False, so the TI is logged as `state 
mismatch` and
   sent to `handle_failure`.
   
   Timeline of one occurrence (UTC, single scheduler, `LocalExecutor`, several 
hundred
   deferrable sensors in one DAG run):
   
       04:01:22.031  TI -> deferred            (worker exits; executor will 
report success)
       04:01:23.377  trigger fired, success
       04:01:25.648  TI -> running             (resumed attempt starts, 
next_method cleared)
       04:01:28.516  state mismatch            (stale defer-exit success 
drained here)
       04:01:28.520  TI -> failed
       04:01:33.967  resumed worker heartbeat -> 409, process killed
   
       Executor LocalExecutor(parallelism=32) reported that the task instance
       <TaskInstance: <dag_id>.<sensor_task_id> ... [running] ti_id=...>
       finished with state success, but the task instance's state attribute is 
running.
   
   Why the current branch does not catch it:
   
   - `ti.queued_by_job_id != job_id` is False — the same scheduler handled both.
   - `executor.has_task(ti)` is False — both attempts share the same 
`TaskInstanceKey`
     (`try_number` does not increment on resume), and the defer-exit event has 
already
     removed that key from the executor's bookkeeping.
   - The resume-after-defer condition is False on two counts: it requires
     `ti.state in (SCHEDULED, QUEUED)` and `ti.next_method is not None`, and by 
the time the
     resumed attempt is running the state is `RUNNING` and `next_method` has 
been cleared.
   
   So it falls through and the task is failed. The in-code comment above the 
guard says the
   `RUNNING` case "is handled by the scheduler detecting task instances without 
heartbeats",
   which does not apply here: the resumed attempt is alive and heartbeating 
normally — the
   problem is the stale event, not a missing heartbeat.
   
   With `retries: 0` on the affected tasks there is no second attempt, so a 
task whose
   deferred work completed successfully ends as `failed` and takes the DAG run 
with it.
   
   Not deterministically reproducible: anything that makes the scheduler slower 
to drain the
   event buffer widens the window. It shows up with
   
   - one DAG with several hundred deferrable sensors and `retries: 0`,
   - an executor sharing the host with the scheduler,
   - a DAG file whose import is slow enough that many workers are starting at 
once,
     competing for the same CPU as the scheduler loop.
   
   The deterministic fingerprint is a `state mismatch` row in `log` immediately 
followed by
   `failed`, for a task whose trigger event was `success`.
   
   **Differences and linkages with current tickets**
   
   - #23824 / #23846 (CLOSED): the original 2.x version of this race, defer 
path.
   - #66374 (CLOSED): the scheduled-state variant, defer path. Fixed by #66431, 
backported
     by #67089.
   - #67287 (CLOSED): the queued-state variant of the same defer path, fixed by 
#68741.
   - #71172 (OPEN): the reschedule-mode sensor path. Same shape of race, 
different path; the
     fix proposed there is gated on a `TaskReschedule` row, which never exists 
here.
   - This one: the defer path where the resumed attempt has already reached 
`RUNNING`. Both
     merged fixes are gated on `ti.state in (SCHEDULED, QUEUED)` **and**
     `next_method is not None`, so neither applies.
   
   
   ### What you think should happen instead?
   
   A stale executor success from a defer-exit should never fail a task instance 
that the
   trigger has already resumed, regardless of whether the resumed attempt is 
`SCHEDULED`,
   `QUEUED` or already `RUNNING`.
   
   Widening the state tuple is not enough on its own, because `next_method` is 
already
   cleared once the resumed attempt runs, so neither field identifies the case 
any more.
   Two directions that would:
   
   1. Make the stale event stop matching: bump the attempt identifier on resume 
so the
      defer-exit event no longer matches `buffer_key` for the current attempt.
   2. Compare timestamps: ignore an executor success whose TI is `RUNNING` and 
whose
      current attempt started after the event was produced.
   
   
   ### Operating System
   
   Ubuntu 22.04 (Linux 5.15)
   
   ### Deployment
   
   Docker-Compose
   
   ### Apache Airflow Provider(s)
   
   _No response_
   
   ### Versions of Apache Airflow Providers
   
   Not provider-specific. Reproduced with astronomer-cosmos 1.15.1 deferrable 
sensors, but
   the mechanism is executor/scheduler-side and provider-independent.
   
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   _No response_
   
   ### Helm Chart configuration
   
   _No response_
   
   ### Docker Image customizations
   
   Custom image built on the official Airflow base image; no changes to 
scheduler,
   executor or Task SDK code.
   
   ### Anything else?
   
   Twice in three days on the same DAG, on a different sensor each time. Our 
workaround is
   to stop deferring those sensors, which removes the defer-exit event entirely.
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


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