Repository navigation
Fix scheduler crash when retrying a task that timed out or was killed externally - #75472
Merged
shahar1 merged 1 commit intoOct 24, 2026
Merged
Conversation
… externally The database callback sink committed and closed the scheduler's scoped session when sending a callback, detaching the task instance the scheduler was about to retry. Archiving the attempt then wrote nothing, and inserting the next attempt hit the task_instance_current_key unique constraint, which stopped the scheduler. The sink now adds the request to the caller's session, and the scheduler commits it. The Dag-lookup-failure branch of executor event processing passes its session to set_state for the same reason.
kaxil
requested review from
XD-DENG,
ashb,
dheerajturaga,
hussein-awala,
o-nikolas and
pierrejeambrun
as code owners
October 23, 2026 13:21
shahar1
approved these changes
Oct 23, 2026
vatsrahul1001
approved these changes
Oct 24, 2026
vatsrahul1001
enabled auto-merge (squash)
October 24, 2026 17:15
shahar1
disabled auto-merge
October 24, 2026 19:53
Contributor
Backport successfully created: v3-4-testNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
|
vatsrahul1001
pushed a commit
that referenced
this pull request
Oct 26, 2026
…r was killed externally (#75472) The database callback sink committed and closed the scheduler's scoped session when sending a callback, detaching the task instance the scheduler was about to retry. Archiving the attempt then wrote nothing, and inserting the next attempt hit the task_instance_current_key unique constraint, which stopped the scheduler. The sink now adds the request to the caller's session, and the scheduler commits it. The Dag-lookup-failure branch of executor event processing passes its session to set_state for the same reason. (cherry picked from commit 976c1d0) Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001
pushed a commit
that referenced
this pull request
Oct 26, 2026
…r was killed externally (#75472) (#75582) The database callback sink committed and closed the scheduler's scoped session when sending a callback, detaching the task instance the scheduler was about to retry. Archiving the attempt then wrote nothing, and inserting the next attempt hit the task_instance_current_key unique constraint, which stopped the scheduler. The sink now adds the request to the caller's session, and the scheduler commits it. The Dag-lookup-failure branch of executor event processing passes its session to set_state for the same reason. (cherry picked from commit 976c1d0) Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
1 task done
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 scheduler stops with
UniqueViolationontask_instance_current_keywhen it fails a task instance that still has retries left, from either of two paths:running(for example a task process killed externally): only when the task hason_failure_callback,on_retry_callbackor email alerts.Cause
Both paths call
executor.send_callback(request)beforeti.handle_failure(...).DatabaseCallbackSink.sendwas@provide_sessionand received no session.create_session()therefore handed it the scheduler loop's own thread-local scoped session, and the sink committed and closed that session. Closing expunges everything, so the task instance about to be retried is detached.prepare_db_for_next_trythen callsarchive(), which setsworking_set = Noneand flushes, but nothing is written for a detached instance. The insert of the next attempt collides with the row that is still current.The sink has committed the caller's scoped session since #21731 (Airflow 2.3), and that was harmless until #74222. That PR made retries archive the attempt and insert a new row, which relies on
archive()updating the row through the ORM. 3.4.0b1 and 3.4.0b2 are affected; 3.3.x is not. This should be backported tov3-4-test.Fix
DatabaseCallbackSink.sendnow adds the request to the caller's session (the thread's scoped session when none is passed) and leaves the commit to the caller. Every scheduler path that sends callbacks commits afterwards anyway:create_session()@provide_session_do_scheduling_do_schedulingwas the one caller that relied on the sink's commit. After sending Dag-level callbacks it runs the critical section, which expunges the session and can roll back.The Dag-lookup-failure branch of
_process_executor_eventscalledti.set_state(state)without a session, which committed and closed the same shared session and detached the rest of the batch. It now passessession=session.Why not pass the session through
executor.send_callback? Executors don't take database sessions in Airflow 3, and provider executors overridesend_callback(request)with the released signature. The sink is the scheduler-side piece that writes to the database, so that is where the commit comes out.Why not call
handle_failurebeforesend_callback? The close would still detach every task instance processed later in the same batch.The new tests cover the callback, email, heartbeat-timeout and Dag-lookup-failure cases. Each fails on
mainwith the sameIntegrityError.Reproduction
Postgres, LocalExecutor,
[scheduler] task_instance_heartbeat_timeout = 20. The task freezes its supervisor so heartbeats stop, the way a hung worker would:On
main(ccbd215), the scheduler loop dies on the first timeout:With this change, the task goes to retry and the scheduler keeps running:
Known issues
TaskInstance.save_to_db, whichhandle_failurecalls, still commits the session it is given. So the scan's row locks are released after the first failed task instance in a batch, as they were before this change. This PR only removes the close that detached the batch.