This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 4c98adf9e20 Skip the scheduler's Dag lookup for Dag runs with no 
callback to send (#74835)
4c98adf9e20 is described below

commit 4c98adf9e20a3cd00d244ee6e02130952150fa85
Author: Kaxil Naik <[email protected]>
AuthorDate: Fri Oct 16 15:10:41 2026 +0100

    Skip the scheduler's Dag lookup for Dag runs with no callback to send 
(#74835)
    
    After each scheduling pass, the scheduler looped over every examined Dag
    run and loaded its Dag before handing the callback to the sink, even when
    the run produced no callback. For Dags in an unversioned bundle such as the
    dags folder, each of those lookups queries the latest dag_version. Have
    _schedule_all_dag_runs return only the runs that have a callback, matching
    the paused-Dag path, which already sends only real callbacks.
---
 .../src/airflow/jobs/scheduler_job_runner.py       |  9 +++---
 airflow-core/tests/unit/jobs/test_scheduler_job.py | 36 ++++++++++++++++------
 2 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/airflow-core/src/airflow/jobs/scheduler_job_runner.py 
b/airflow-core/src/airflow/jobs/scheduler_job_runner.py
index 40e1fa976ff..c5e7732f6d8 100644
--- a/airflow-core/src/airflow/jobs/scheduler_job_runner.py
+++ b/airflow-core/src/airflow/jobs/scheduler_job_runner.py
@@ -3211,13 +3211,14 @@ class SchedulerJobRunner(BaseJobRunner, LoggingMixin):
         guard: CommitProhibitorGuard,
         dag_runs: Iterable[DagRun],
         session: Session,
-    ) -> list[tuple[DagRun, DagCallbackRequest | None]]:
-        """Make scheduling decisions for all `dag_runs`."""
-        callback_tuples = []
+    ) -> list[tuple[DagRun, DagCallbackRequest]]:
+        """Make scheduling decisions for all `dag_runs` and return the 
callbacks to send."""
+        callback_tuples: list[tuple[DagRun, DagCallbackRequest]] = []
         for run in dag_runs:
             try:
                 callback = self._schedule_dag_run(run, session=session)
-                callback_tuples.append((run, callback))
+                if callback is not None:
+                    callback_tuples.append((run, callback))
             except DBAPIError:
                 raise  # let @retry_db_transaction handle DB errors
             except Exception:
diff --git a/airflow-core/tests/unit/jobs/test_scheduler_job.py 
b/airflow-core/tests/unit/jobs/test_scheduler_job.py
index 7118d08edc6..858ffc87c4c 100644
--- a/airflow-core/tests/unit/jobs/test_scheduler_job.py
+++ b/airflow-core/tests/unit/jobs/test_scheduler_job.py
@@ -5230,7 +5230,7 @@ class TestSchedulerJob:
 
             def mock_schedule_dag_run(*args, **kwargs):
                 mock_guard.reset_mock()
-                return None
+                return mock.create_autospec(DagCallbackRequest, instance=True)
 
             def mock_send_dag_callbacks_to_processor(*args, **kwargs):
                 
mock_guard.return_value.__enter__.return_value.commit.assert_called()
@@ -5272,12 +5272,30 @@ class TestSchedulerJob:
 
         self.job_runner._do_scheduling(session)
 
-        # Verify Callback is not set (i.e is None) when no callbacks are set 
on DAG
-        self.job_runner._send_dag_callbacks_to_processor.assert_called_once()
-        call_args = 
self.job_runner._send_dag_callbacks_to_processor.call_args.args
-        assert call_args[0].dag_id == dr.dag_id
-        assert call_args[1] is None
+        # No callback is built when no callbacks are set on the Dag, so 
nothing is sent
+        self.job_runner._send_dag_callbacks_to_processor.assert_not_called()
+
+        session.rollback()
+
+    @conf_vars({("scheduler", "use_job_schedule"): "False"})
+    def test_do_scheduling_skips_dag_lookup_for_runs_without_callback(self, 
dag_maker, session):
+        """The callback loop does not load the Dag for runs that produced no 
callback."""
+        with dag_maker(
+            
dag_id="test_do_scheduling_skips_dag_lookup_for_runs_without_callback", 
session=session
+        ):
+            EmptyOperator(task_id="dummy")
+        dag_maker.create_dagrun()
+        self.job_runner = SchedulerJobRunner(job=Job())
+
+        with (
+            mock.patch.object(self.job_runner, "_schedule_dag_run", 
autospec=True, return_value=None),
+            mock.patch.object(self.job_runner, 
"_send_dag_callbacks_to_processor", autospec=True) as send,
+            mock.patch.object(self.job_runner.scheduler_dag_bag, 
"get_dag_for_run", autospec=True) as get_dag,
+        ):
+            self.job_runner._do_scheduling(session)
 
+        get_dag.assert_not_called()
+        send.assert_not_called()
         session.rollback()
 
     @pytest.mark.parametrize(("state", "msg"), [[State.SUCCESS, "success"], 
[State.FAILED, "task_failure"]])
@@ -7437,6 +7455,7 @@ class TestSchedulerJob:
         scheduler_job = Job()
         self.job_runner = SchedulerJobRunner(job=scheduler_job, 
executors=[self.null_exec])
 
+        good_callback = mock.create_autospec(DagCallbackRequest, instance=True)
         caplog.clear()
         with (
             caplog.at_level("ERROR", 
logger="airflow.jobs.scheduler_job_runner"),
@@ -7446,7 +7465,7 @@ class TestSchedulerJob:
                 autospec=True,
                 side_effect=[
                     TypeError("simulated crash from corrupted task instance"), 
 # bad_run
-                    None,  # good_run
+                    good_callback,  # good_run
                 ],
             ) as mock_schedule,
         ):
@@ -7456,8 +7475,7 @@ class TestSchedulerJob:
                 result = self.job_runner._schedule_all_dag_runs(guard, 
[bad_run, good_run], session=session)
 
             # The good DAG run should have been processed despite the bad one 
failing
-            assert len(result) == 1
-            assert result[0][0] == good_run
+            assert result == [(good_run, good_callback)]
 
             # Both dag runs should have been attempted
             assert mock_schedule.call_count == 2

Reply via email to