Skip to content

Avoid scheduler crash when periodic maintenance actions fail#69487

Draft
hanxdatadog wants to merge 4 commits into
apache:mainfrom
hanxdatadog:hanx/fix-scheduler-crash-on-periodic-task-exception
Draft

Avoid scheduler crash when periodic maintenance actions fail#69487
hanxdatadog wants to merge 4 commits into
apache:mainfrom
hanxdatadog:hanx/fix-scheduler-crash-on-periodic-task-exception

Conversation

@hanxdatadog

@hanxdatadog hanxdatadog commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

  • Adds an opt-in non_fatal: bool = False parameter to EventScheduler.call_regular_interval. When True, an exception raised by the periodic action is logged (self.log.exception(...)) and swallowed instead of propagating; the next cycle is still scheduled either way. Default is False, preserving current behavior for existing callers.
  • Enables non_fatal=True for the two SchedulerJobRunner periodic maintenance callbacks registered via call_regular_interval that had no exception handling at all: _remove_unreferenced_triggers and _reap_stale_connection_tests.
  • Logs Failed to run periodic action <name> due to <exception>; will retry on the next cycle on the caught-exception path — matches the existing Failed to <thing> due to %s house style already used by _update_dag_run_state_for_paused_dags, and states why a single occurrence is safe to ignore.

Motivation:
EventScheduler.call_regular_interval's inner repeat() closure calls the registered action with no exception handling, so a single transient failure (e.g. a DB statement_timeout during _remove_unreferenced_triggers's DELETE) propagates all the way up and crashes the whole SchedulerJob. In practice this means: the scheduler process dies with a critical-level log, and every task instance it was tracking gets swept into the orphaned-task-adoption path on the next scheduler startup — a heavyweight, disruptive recovery flow triggered by what should have been a harmless, skippable cycle of a periodic cleanup job. This is inconsistent with the existing pattern elsewhere in SchedulerJobRunner_update_dag_run_state_for_paused_dags already wraps its own body in except Exception as e: # should not fail the scheduler, and the same crash-avoidance philosophy has precedent in merged PRs like #62893 (isolating per-DagRun failures so one bad DagRun can't crash the scheduler). EventScheduler is documented as a "general purpose" utility, so rather than making it always swallow exceptions (which would silently change behavior for any future caller that wants failures to propagate), the fix is an explicit opt-in.

Testing:

  • Unit tests: 2 new tests in test_event_scheduler.py — one confirms the default (no flag) still propagates the action's exception, one confirms non_fatal=True swallows it and still schedules the next cycle.

Notes:

  • Named non_fatal rather than catch_exceptions to be outcome-focused (this failure isn't fatal to the scheduler) rather than mechanism-focused, matching existing "non-fatal" terminology already used elsewhere in the codebase (dag_processing/importers/base.py).

Was generative AI tooling used to co-author this PR?
  • Yes (Claude Code)

_remove_unreferenced_triggers and _reap_stale_connection_tests are
registered as periodic scheduler maintenance callbacks via
EventScheduler.call_regular_interval, which has no exception handling
around the callback. A single transient failure (e.g. a DB
statement_timeout) propagates and crashes the whole SchedulerJob.

Add an opt-in catch_exceptions flag (default False, preserving current
behavior) that logs and swallows an exception from the periodic action
instead of letting it propagate, so one bad cycle doesn't take down the
scheduler; the next cycle is still scheduled either way. Enable it for
the two maintenance callbacks above.
@boring-cyborg boring-cyborg Bot added the area:Scheduler including HA (high availability) scheduler label Jul 6, 2026
non_fatal reads better at call sites (e.g. non_fatal=True) than the
mechanism-focused catch_exceptions, and matches existing "non-fatal"
terminology already used elsewhere in the codebase for the same
crash-avoidance concept (see dag_processing/importers/base.py).
…pt to investigate

Log "Failed to run periodic action <name> due to <exception>. Please
investigate if this is persistent." instead of a bare "Exception
raised in periodic action <repr>", making the log actionable on its
own without needing to open the traceback to identify what failed.
_update_dag_run_state_for_paused_dags already uses "Failed to <thing>
due to %s" (scheduler_job_runner.py); match that instead of inventing
new phrasing. Drop "please investigate if this is persistent" -- it's
not actionable (nothing here can tell whether a single occurrence is
persistent) and no other log line in this codebase phrases itself as
a directive to the reader. Add "will retry on the next cycle" instead,
since that's genuinely new information explaining why the exception
is safe to swallow.
@hanxdatadog hanxdatadog changed the title Add catch_exceptions option to EventScheduler.call_regular_interval Avoid scheduler crash when periodic maintenance actions fail Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Scheduler including HA (high availability) scheduler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant