Skip to content

fix(scheduler): swallow per-entry sync errors so beat stays up - #45

Merged
paktusov merged 2 commits into
mainfrom
fix/scheduler-sync-broad-except
Aug 13, 2026
Merged

fix(scheduler): swallow per-entry sync errors so beat stays up#45
paktusov merged 2 commits into
mainfrom
fix/scheduler-sync-broad-except

Conversation

@paktusov

Copy link
Copy Markdown
Collaborator

Problem

The multitenant sync() override in DatabaseScheduler catches only (KeyError, ObjectDoesNotExist) inside the per-entry try block. Any other exception — for example set_schema_from_context({"space_code": schema}) raising a plain Exception when a stale entry’s schema is no longer in get_all_tenant_schemas() — escapes sync(), propagates through DatabaseScheduler.schedule and beat.tick(), and kills the beat process.

Impact (observed)

Realm realm04pdn, space space0uph9:

  • workflow-scheduler pod restarted 254 times in the month after the previous scheduler fix was deployed (2026-07-13 → 2026-08-13).
  • 2026-08-11: a scheduler restart coincided with a user cancelling four in-progress helper-calculate-portfolio-price-history workflows. After each restart beat treated portfolio_history as due, dispatched workflow.tasks.workflows.execute, crashed again on the same bad entry, restarted, dispatched again.
  • Result: 316 portfolio_history workflows fired in 89 minutes, all with the same payload (T-22 × 30 portfolios), before the loop finally settled. To the user this looked like an uncontrolled cascade.

Fix

Add an outer except Exception inside the per-entry try block that mirrors the existing (KeyError, ObjectDoesNotExist) branch: log the entry name and error, add it to _failed so it gets re-queued for the next sync, and continue with the rest of _dirty. One healthy schedule can no longer be taken down by an unrelated broken entry.

Diff is 15 lines (comment + except), no behavior change on the happy path.

Rollout notes

  • Deploying this fix will stop the ~254 restarts/month on workflow-scheduler-realm04pdn. Once beat is stable, last_run_at gets persisted normally on each tick and the "keep re-firing because we crashed before saving" pattern goes away.
  • No schema changes, no migrations, no data touched.

The multitenant sync() override caught only KeyError/ObjectDoesNotExist
inside the per-entry try block, so any other exception raised while
switching schema or saving an entry (e.g. a stale schema entry whose
name split gives a space_code no longer in get_all_tenant_schemas,
which makes set_schema_from_context raise plain Exception) propagates
out of sync(), out of DatabaseScheduler.schedule, out of beat.tick(),
and kills the beat process.

Observed on realm04pdn/space0uph9 with the workflow-scheduler pod:
254 restarts in the month after the previous scheduler fix was
deployed (2026-07-13). On 2026-08-11 the restart coincided with a
user cancelling four in-progress price-history workflows; after each
restart beat treated portfolio_history as due, dispatched it, crashed
again on the same bad entry, restarted, dispatched again -- 316
portfolio_history workflows fired in 89 minutes with identical
payload before the loop broke.

Fix: log the offending entry and re-queue it via _failed, matching
the existing (KeyError, ObjectDoesNotExist) behaviour, instead of
letting the exception escape sync(). One healthy schedule can no
longer be taken down by an unrelated broken entry.
@paktusov
paktusov merged commit 737a244 into main Aug 13, 2026
2 checks passed
@paktusov
paktusov deleted the fix/scheduler-sync-broad-except branch August 13, 2026 04:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant