Documentation
¶
Overview ¶
Package scheduler runs recurring work on a cron expression or a fixed interval, with the two properties that decide whether scheduled work is safe to deploy on more than one replica.
Singleton one replica runs the job, not all of them. Overlap a slow run does not start a second copy of itself.
Both failure modes are invisible in development, where there is one replica and every job finishes in a millisecond. In production they are how a nightly billing sweep charges three times, and how a reconciler that normally takes 20 seconds piles up 200 copies the night it takes 20 minutes.
Index ¶
- type Job
- type Scheduler
- func (s *Scheduler) Add(j Job) error
- func (s *Scheduler) Jobs() []string
- func (s *Scheduler) MustAdd(j Job)
- func (s *Scheduler) OnReady(ctx context.Context) error
- func (s *Scheduler) OnShutdown(ctx context.Context) error
- func (s *Scheduler) Start(parent context.Context) error
- func (s *Scheduler) Stop(ctx context.Context) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Job ¶
type Job struct {
// Name identifies the job in logs and is the lock key. Required, unique.
Name string
// Cron is a standard 5-field expression ("0 3 * * *"). Set this or Every.
//
// Descriptors are supported: @hourly, @daily, @weekly, @every 1h30m.
Cron string
// Every runs at a fixed interval from start-up. Set this or Cron.
Every time.Duration
// Run does the work. It MUST honour ctx — a job that ignores its deadline
// cannot be stopped, and shutdown will wait for it until the drain
// timeout and then abandon it mid-write.
Run func(ctx context.Context) error
// Singleton makes exactly one replica run each occurrence, via the
// scheduler's Locker. Leave it off ONLY for work that is safe to run N
// times — a cache warm, a metric refresh.
Singleton bool
// LockTTL must exceed the job's worst-case runtime. Too short and the
// lock expires mid-run, letting a second replica start: the exact thing
// Singleton was for. Defaults to Timeout, or 5 minutes.
LockTTL time.Duration
// Timeout bounds one run. Defaults to 5 minutes; zero is not unlimited,
// because an unbounded scheduled job is how a connection pool is
// exhausted overnight.
Timeout time.Duration
// RunOnStart fires once at start-up, in addition to the schedule.
RunOnStart bool
// Jitter spreads the start across replicas by up to this much. With
// Singleton it avoids a thundering herd on the lock; without it, it
// avoids every replica hitting the database on the same second.
Jitter time.Duration
}
Job is one recurring task.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler owns the running jobs. Register it as a lifecycle hook: it starts in OnReady and drains in OnShutdown.
func New ¶
New builds a scheduler. Pass a lock.Locker for Singleton jobs; without one, Singleton jobs are refused at Add rather than silently running everywhere.
func (*Scheduler) Add ¶
Add registers a job. It validates eagerly, so a malformed cron expression fails the boot instead of being discovered the night it was supposed to run.
func (*Scheduler) MustAdd ¶
MustAdd is Add, panicking on an invalid job. Jobs are declared in code at boot, so a panic there is the right failure.
func (*Scheduler) OnReady ¶
OnReady implements lifecycle.Ready: it starts every job.
Ready rather than Init on purpose — a reconciler that starts before the listener races the boot it is part of, and will happily run against half-constructed dependencies.
func (*Scheduler) OnShutdown ¶
OnShutdown implements lifecycle.Shutdowner: stop scheduling and wait for the runs in flight, up to the drain deadline.