Skip to content

feat(scheduler): cron parser + in-process tick loop (PRE-6)#1574

Merged
nextlevelshit merged 1 commit into
mainfrom
wave/pre6-scheduler
Apr 29, 2026
Merged

feat(scheduler): cron parser + in-process tick loop (PRE-6)#1574
nextlevelshit merged 1 commit into
mainfrom
wave/pre6-scheduler

Conversation

@nextlevelshit
Copy link
Copy Markdown
Collaborator

Closes #1571. Phase 0 child of Epic #1565.

What

New bounded context `internal/scheduler/` with two components:

  • `cron.go` — 5-field cron parser. Supports ``, `N`, `N-M`, `/N`, `N-M/N`, comma lists. cron(8) DOM/DOW OR semantics. `NextFire` walks minute-by-minute capped at 5 years to surface impossible expressions.
  • `scheduler.go` — `Scheduler` with `Start`/`Stop`/`Tick`. Reads `state.ScheduleStore` (PRE-5), fires due jobs through a pluggable `Dispatcher` interface. Default tick 30s. Fires once on Start so booted servers don't wait the full interval for overdue rows.

Behaviour notes

  • Bad cron expression → advance `next_fire_at` by 24h to prevent tick churn
  • Failed dispatch → still advance `next_fire_at` so a chronically-broken dispatcher doesn't hot-loop
  • `Tick` exposed for deterministic test driving
  • Independent of runner package — boot path supplies the Dispatcher

Tests

  • `cron_test.go` — parser shapes, NextFire correctness (incl. weekday-only, DOM/DOW OR), strictly-after-now, impossible-expression error
  • `scheduler_test.go` — fakeStore-driven Tick (due-only, future-skip, bad-cron, dispatch-err), Start fires immediately

659 LOC, 4 new files.

Acceptance vs #1571

  • Cron expressions parsed (5-field standard)
  • Tick loop fires due jobs, marks them in DB (next_fire_at advance + last_run_id)
  • Graceful shutdown (Stop drains in-flight tick with 5s timeout)
  • Tests cover edge cases (bad cron, dispatch err, impossible expr, future-skip)

New bounded context for cron-driven recurring runs. Reads schedule rows
written via state.ScheduleStore (PRE-5 #1568), fires due jobs through a
caller-supplied Dispatcher, advances next_fire_at via the cron parser.

Components:

- internal/scheduler/cron.go — 5-field cron parser supporting *, N, N-M,
  */N, N-M/N, comma lists. cron(8) day-of-month/day-of-week OR semantics
  when both fields are explicit. NextFire walks forward minute-by-minute,
  capped at 5 years to surface impossible expressions like "0 0 31 2 *".

- internal/scheduler/scheduler.go — Scheduler struct with Start/Stop
  lifecycle, configurable tick interval (default 30s), pluggable
  Dispatcher interface so this package stays independent of the
  runner/executor wiring. Tick() exposed for deterministic test driving.
  Fires once on Start so freshly-booted servers don't wait the full tick
  before processing overdue rows.

Behaviour notes:
- Bad cron expressions advance next_fire_at by 24h to prevent churn
  while the operator fixes the row.
- Failed Dispatch still advances next_fire so a chronically-broken
  dispatcher doesn't hot-loop.

Tests cover parser shapes (every-min/top-of-hour/weekday/range-step),
NextFire correctness (incl. impossible-expression error), tick loop
(due-only, future-skip, bad-cron, dispatch-err, Start fires immediately).

Closes #1571.
@nextlevelshit nextlevelshit merged commit 798c81f into main Apr 29, 2026
4 checks passed
@nextlevelshit nextlevelshit deleted the wave/pre6-scheduler branch April 29, 2026 19:50
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.

PRE-6: Scheduler (in-proc tick loop, cron parser)

1 participant