Repository navigation
Correlate executor task events by attempt UUID - #73916
Merged
Merged
Conversation
ashb
requested review from
XD-DENG,
dheerajturaga,
hussein-awala,
o-nikolas and
pierrejeambrun
as code owners
September 29, 2026 19:20
A delayed executor event must identify the attempt that produced it after clearing or retrying replaces the live task instance. Reconcile scheduler events by UUID and retain the retiring identity through restart completion and heartbeat cleanup. Core upgrades can retain older executor providers, so preserve their coordinate-keyed interfaces. Capture UUIDs before submission or adoption and translate legacy events from those snapshots without looking up the current row by coordinates. Retain accepted adoptions across empty event drains, including providers that track adopted work only in private maps. LocalExecutor opts into UUID keys end to end. Legacy providers cannot distinguish attempts that reuse the entire coordinate key; features that require that isolation must require the UUID capability until providers migrate their submission, adoption, result decoding and cleanup paths.
ashb
force-pushed
the
executor-ti-identity
branch
from
September 29, 2026 20:37
b54ccf0 to
4fdd03a
Compare
kaxil
reviewed
Sep 29, 2026
kaxil
approved these changes
Sep 29, 2026
ashb
force-pushed
the
executor-ti-identity
branch
from
September 30, 2026 21:05
9f29ed6 to
7ea8db2
Compare
shahar1
reviewed
Oct 1, 2026
shahar1
approved these changes
Oct 1, 2026
shahar1
reviewed
Oct 1, 2026
Member
Author
|
Argh damn GH UI. It hadn't loaded the test results, so I thought it was green when I hit merge :( |
Member
Author
|
Huh, thought it seems fine in main. I'll keep an eye on it. |
1 task done
ashb
added a commit
that referenced
this pull request
Oct 3, 2026
…g` list Since #73916 landed, BaseExecutor now keeps a UUID-to-coordinates map for each attempt and drops an entry once the key is no longer queued, running or in the event buffer. The issue was that LocalExecutor never added dispatched work to `running`, so the entry was dropped while the task was still executing. The scheduler then received the final event with no coordinates ("Received executor event with state success for task instance <uuid> (coordinates=None)"). This PR fixes that, and addresses a few gotchas that could, in edge cases around callbacks or post-task finalization, lead to a forever-dead/locked worker slot. Holding the key in `running` until the worker finishes needs LocalExecutor to know when that is, and workers share one activity queue so the parent cannot tell which worker took which workload. Results now carry the worker's pid and START tells the parent which pid owns the key. That lets it: - release the slot only on a terminal result from the owning worker, ignoring results from unknown pids or pids that don't own the key - fail the workload when its worker dies, instead of leaving it in `running` until the scheduler's heartbeat timeout A resumed task reuses its key, so a per-key dispatch count stops the old run's terminal result from releasing the new run's slot.
ashb
added a commit
that referenced
this pull request
Oct 3, 2026
…g` list Since #73916 landed, BaseExecutor now keeps a UUID-to-coordinates map for each attempt and drops an entry once the key is no longer queued, running or in the event buffer. The issue was that LocalExecutor never added dispatched work to `running`, so the entry was dropped while the task was still executing. The scheduler then received the final event with no coordinates ("Received executor event with state success for task instance <uuid> (coordinates=None)"). This PR fixes that, and addresses a few gotchas that could, in edge cases around callbacks or post-task finalization, lead to a forever-dead/locked worker slot. Holding the key in `running` until the worker finishes needs LocalExecutor to know when that is, and workers share one activity queue so the parent cannot tell which worker took which workload. Results now carry the worker's pid and START tells the parent which pid owns the key. That lets it: - release the slot only on a terminal result from the owning worker, ignoring results from unknown pids or pids that don't own the key - fail the workload when its worker dies, instead of leaving it in `running` until the scheduler's heartbeat timeout A resumed task reuses its key, so a per-key dispatch count stops the old run's terminal result from releasing the new run's slot.
aws-airflow-bot
pushed a commit
to aws-mwaa/upstream-to-airflow
that referenced
this pull request
Oct 3, 2026
…g` list Since apache#73916 landed, BaseExecutor now keeps a UUID-to-coordinates map for each attempt and drops an entry once the key is no longer queued, running or in the event buffer. The issue was that LocalExecutor never added dispatched work to `running`, so the entry was dropped while the task was still executing. The scheduler then received the final event with no coordinates ("Received executor event with state success for task instance <uuid> (coordinates=None)"). This PR fixes that, and addresses a few gotchas that could, in edge cases around callbacks or post-task finalization, lead to a forever-dead/locked worker slot. Holding the key in `running` until the worker finishes needs LocalExecutor to know when that is, and workers share one activity queue so the parent cannot tell which worker took which workload. Results now carry the worker's pid and START tells the parent which pid owns the key. That lets it: - release the slot only on a terminal result from the owning worker, ignoring results from unknown pids or pids that don't own the key - fail the workload when its worker dies, instead of leaving it in `running` until the scheduler's heartbeat timeout A resumed task reuses its key, so a per-key dispatch count stops the old run's terminal result from releasing the new run's slot.
eladkal
pushed a commit
that referenced
this pull request
Oct 5, 2026
…g` list (#74142) * Improve LocalExecutor bookkeeping: correctly add tasks to the `running` list Since #73916 landed, BaseExecutor now keeps a UUID-to-coordinates map for each attempt and drops an entry once the key is no longer queued, running or in the event buffer. The issue was that LocalExecutor never added dispatched work to `running`, so the entry was dropped while the task was still executing. The scheduler then received the final event with no coordinates ("Received executor event with state success for task instance <uuid> (coordinates=None)"). This PR fixes that, and addresses a few gotchas that could, in edge cases around callbacks or post-task finalization, lead to a forever-dead/locked worker slot. Holding the key in `running` until the worker finishes needs LocalExecutor to know when that is, and workers share one activity queue so the parent cannot tell which worker took which workload. Results now carry the worker's pid and START tells the parent which pid owns the key. That lets it: - release the slot only on a terminal result from the owning worker, ignoring results from unknown pids or pids that don't own the key - fail the workload when its worker dies, instead of leaving it in `running` until the scheduler's heartbeat timeout A resumed task reuses its key, so a per-key dispatch count stops the old run's terminal result from releasing the new run's slot. * fixup! Improve LocalExecutor bookkeeping: correctly add tasks to the `running` list * fixup! Improve LocalExecutor bookkeeping: correctly add tasks to the `running` list
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 executor event sub-system in Airflow is a little bit confusing. This hopefully tidies things up a little bit. Previously events were keyed/stored by their "coordinates", the 4-tuple (dag_id, run_id, task_id, map_index) (and in some places a 5-tuple also including try_number). That is unwieldy, and since 3.0.0 a better option is available: a single UUID that uniquely identifies the TI try.
This changes the event system to use that, with the net effect being that a deleyed event from a previous try is no longer able to to update the state for a newly scheduled replacement try. I have no evidence this has ever happened, but it is conceptually cleaner, and lets us make some important assumptions going forward with the introduction of Task Loops.
Since
apache-airflow-corecan be upgrade by itself and executors from not-yet-updated providers might be in use, we preserve their coordinate-keyed interfaces by capturing UUIDs before submission or adoption and translate legacy events from those snapshots without looking up the current row by coordinates.LocalExecutor opts into UUID keys end to end in this change. Provider-based executors will be their own PRs.
Was generative AI tooling used to co-author this PR?
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.Stack created with GitHub Stacks CLI • Give Feedback 💬