xchwan commented on code in PR #75708:
URL: https://github.com/apache/airflow/pull/75708#discussion_r4511120037


##########
airflow-core/src/airflow/serialization/definitions/dag.py:
##########
@@ -861,6 +862,15 @@ def _process_dagrun_deadline_alerts(
                 )
                 stats.incr("deadline_alerts.deadline_creation_failed", 
tags=metrics_tags)
 
+            # An alert can roll the session back, discarding the pending 
DagRun. Carrying on would
+            # add Deadlines that violate the dagrun_id foreign key, and 
returning normally would let
+            # the caller treat the missing run as created.
+            if orm_dagrun not in session:

Review Comment:
   Thanks, this makes sense: evaluating alerts on their own session fixes the 
cause, so a rollback in an alert only affects that session and the run (and 
other Dags' runs in the same tick) survive.
   
   Plan:
   
   1. Resolve the interval and call `evaluate_with` on a 
`create_session(scoped=False)` session, passing a snapshot of the run instead 
of the pending ORM object. The snapshot carries `dag_id`, `logical_date` and 
`queued_at`, which are the fields `DeadlineDagRunProtocol` guarantees 
(`queued_at` is set at construction, so no DB read is needed). `Deadline` rows 
are still added on the scheduler's session.
   2. Keep the `orm_dagrun not in session` check and `DagRunRolledBackError` as 
a backstop.
   3. Document the contract in `deadline-alerts.rst`.
   
   Some things I considered:
   
   - **Read-only, no auto-commit.** `create_session` commits on exit, so a 
write from a reference would persist even if the run creation later fails. I'd 
use a plain `NonScopedSession()` that is always rolled back and closed, one per 
Dag run rather than per alert.
   - **Locks.** The scheduler's session holds the flushed `DagRun` and the 
`DagModel` row lock, so a reference that takes a row lock or writes through the 
new session would wait on its own process. Plain reads (`Variable`, 
`AverageRuntimeDeadline`) are unaffected. I'd document this rather than enforce 
it.
   - **Snapshot fields.** A reference that reads something beyond the three 
guaranteed fields (`conf`, `start_date`) would hit `AttributeError`, which the 
per-alert handler turns into a skipped alert. I'd document the guaranteed 
fields rather than widen the snapshot.
   - **Bypassing the passed session.** A reference that uses `provide_session`, 
`create_session()` or `settings.Session()` gets the scheduler's scoped session. 
`commit()` there already fails under `prohibit_commit`; a `rollback()` can't be 
isolated from inside this function, so the backstop turns it into a visible 
failure instead of an orphaned `Deadline`.
   - **Other callers.** `create_dagrun` is also used by API trigger, backfill 
and `dags test`, so I'll check those, including SQLite in-memory where a second 
connection can't see the first one's data.
   
   This doesn't address #59120 (DB errors on the scheduler's session in the 
create loop). That is a separate problem and needs a savepoint-friendly 
`prohibit_commit`.
   
   Does this scope work for you, with the rest handled by documentation and the 
backstop? I'll update the tests accordingly.
   
   ---
   Drafted-by: Claude Code (Sonnet 5.5); reviewed by @xchwan



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