Repository navigation
Conversation
|
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
|
2a820f1 to
8c3adff
Compare
|
@dstandish @hussein-awala Could you approve the CI run? Thanks! |
|
@potiuk @dstandish @hussein-awala Friendly reminder - could someone approve the CI run? Thanks! |
57f0d9b to
295071e
Compare
|
Force-pushed with a fix for a false-positive path in the mismatch check. |
potiuk
left a comment
There was a problem hiding this comment.
Thanks — detecting supervisor/subprocess drift is worth doing, and I checked the placement: the call sits between running_triggers.discard(id) for finished triggers (line 594) and running_triggers.update(...) for to_create (line 621), which is exactly the window your docstring describes. Test coverage is good too.
What I'd push back on is the remedy. self.stop = True feeds should_stop() and exits the supervisor run loop, so a single count disagreement takes down the whole Triggerer and every trigger running on it. That's a very large hammer for a condition the code itself describes as only valid inside a narrow window. Detail inline.
As with my other reviews today: these are design questions more than defects, my review was AI-assisted, and I'd rather you formed your own view than took mine as settled. Other maintainers who know the Triggerer supervisor protocol should weigh in before this lands.
Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
295071e to
d0f5f2d
Compare
|
@potiuk -- the three points above are addressed and CI is green. The CHANGES_REQUESTED still left, so a re-look when you get a chance would help. Happy to adjust if the reasoning on the timing window doesn't hold up. @dstandish @hussein-awala -- you're the CODEOWNERS for triggerer_job_runner.py, and this touches the supervisor/runner sync protocol that @potiuk suggested should get a second pair of eyes. Would appreciate a look when you have time. ---Drafted-by: Claude Code (Opus 5); reviewed by @JH0917 before posting |
potiuk
left a comment
There was a problem hiding this comment.
Everything from the last round is addressed, and one of my points was wrong.
The remedy rework is what I was hoping for. Dropping the stranded ids so update_triggers rebuilds them is recoverable in a way self.stop = True was not, and the ERROR log plus the triggers.state_mismatch counter gives operators something to alert on rather than an unexplained restart.
On the bound logger: you are right and I was wrong. I traced it — _register_pipe_readers takes no log parameter, so the log that reaches _handle_request is the module-level structlog.get_logger(logger_name="supervisor") in supervisor.py. Nothing is bound to it, so there is no request context to preserve. Leave it as it is.
On skipping the debounce: your argument holds as far as I can follow it. running_ids is snapshotted before the await, so a trigger finishing mid-window is still in the snapshot and will not be flagged — the race only errs in the safe direction.
One new thing, inline: the running_ids default.
@hussein-awala — you are CODEOWNER for triggerer_job_runner.py and have been in supervisor.py recently. Could you look at the supervisor/runner protocol reasoning here? The open question is whether a single count disagreement can ever be legitimate, which is what decides whether the no-debounce choice is right, and I am not the right person to settle that.
Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
| failures: list[tuple[int, list[str] | None]] | None = None | ||
| finished: list[int] | None = None | ||
| # Ids the runner has a live coroutine for | ||
| running_ids: set[int] = set() |
There was a problem hiding this comment.
An empty set means "the runner has no live coroutines", which is indistinguishable from "this message carried no running_ids at all". In the second case unhandled = self.running_triggers - set() is everything, so the supervisor drops and re-creates every running trigger.
No production path hits this today — process_trigger_events and sanitize_trigger_events both set the field. But the existing tests construct the message without it (e.g. TriggerStateChanges(events=None, failures=None, finished=None) in test_triggerer_job.py), which is exactly that shape. With this carrying backport-to-v3-3-test, I would rather the ambiguity not exist at all:
| running_ids: set[int] = set() | |
| # Ids the runner has a live coroutine for; None when the message carries no information | |
| running_ids: set[int] | None = None |
with an early return in check_for_unhandled_triggers when running_ids is None.
Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
There was a problem hiding this comment.
Applied -- thanks for the review. I agree that if the field is missing, defaulting to None is safer than creating all triggers again. Added a test for the None path.
d0f5f2d to
2388958
Compare
potiuk
left a comment
There was a problem hiding this comment.
This round is good. The running_ids default is what I asked for, and I no longer need the second opinion I asked for last time — I traced it myself.
On the timing window: arun() runs cleanup_finished_triggers() → sync_state_to_supervisor() → create_triggers() → cancel_triggers() in one task, and process_trigger_events snapshots set(self.triggers) before the asend await. So the runner is parked on that await for the whole time the supervisor is comparing, and nothing else in the process mutates self.triggers. The snapshot cannot go stale mid-comparison, which is your argument, and it holds. One disagreement is genuine — no debounce needed.
Also checked that set[int] already crosses this channel (TriggerStateSync.to_cancel), so running_ids needs no new serialization support, and the metrics registry check passes for the new counter. Locally: the 11 new tests pass and the rest of test_triggerer_job.py is unaffected.
Two things to fix before merge:
-
Please add
airflow-core/newsfragments/68792.bugfix.rst. The fix is user-visible (tasks that would have stayeddeferrednow recover) andtriggers.state_mismatchis a new public metric operators will alert on — the two sibling Triggerer metric PRs (#67927, #68521) both carry one. Withbackport-to-v3-3-teston this, the newsfragment is what puts it in the 3.3.x release notes. -
The duplicate id in
finished, inline below.
Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
2388958 to
e259812
Compare
|
Thanks for tracing the timing window yourself. Both items are in: the newsfragment, and the duplicate-id guard with a That was a force-push, so the workflow runs need approving again -- could you Drafted-by: Claude Code (Opus 5); reviewed by @JH0917 before posting |
e259812 to
2938ce1
Compare
|
Can merge post code owners review |
2938ce1 to
dba3b5a
Compare
|
@potiuk Rebased onto main and resolved the conflict -- an append-at-end-of-file Should we still wait for a code owner review here? The note above says it is |
Remove the incorrect difference_update on msg.failures (which would break for serialization failures where the trigger is still running). Instead, include creation-failure IDs in finished_ids so the supervisor naturally removes them from running_triggers via the existing finished processing path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dba3b5a to
2fd0913
Compare
|
This one looks good but @hussein-awala -> maybe you could confirm it ? |
|
One more thing while this waits for the code-owner look: #73454 added a third tracking set,
Could you add Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting |
|
Added It traces exactly as you said. A trigger being released is added to Drafted-by: Claude Code (Opus 5); reviewed by @JH0917 before posting |
seanghaeli
left a comment
There was a problem hiding this comment.
All concerns have been addressed, @potiuk good to merge?
|
Updated the branch and resolved the conflict. @dstandish @hussein-awala code-owner review needed for airflow-core/src/airflow/jobs/triggerer_job_runner.py. |
In rare cases the Triggerer subprocess can lose track of a trigger — it
is assigned in the supervisor's
running_triggersbut no coroutine isactually running. The affected task instance stays in
deferredpermanently.
Changes:
Supervisor compares
len(running_triggers)against the subprocess'sactual trigger count each sync cycle. If they diverge, it shuts down
so the orchestrator can restart and recover stuck tasks.
Creation failures are included in
finished_idsso the supervisorremoves them from
running_triggersnaturally (prevents false-positiveshutdown).
related: #63913
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 4.6) following the guidelines