Documentation
¶
Overview ¶
Package jobs makes scheduled jobs safe to run from several processes and visible when they stop working.
A Guard wraps each run of a job. Before the job starts it takes a Postgres advisory lock named after the job, so a job never runs twice at the same time, whether the second run comes from another process or from the same scheduler firing again while the previous run is still going. After the run it records the outcome in a table (job_runs by default): last start, last success, last error, consecutive failures, duration and skips. Statuses and Handler turn that table into a health report, so a job that silently stopped succeeding shows up even when it never logs an error.
The Guard is scheduler-agnostic: call Run from gocron, robfig/cron, a time.Ticker or anything else.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrSkipped = errors.New("jobs: another run of this job holds the lock")
ErrSkipped is returned by Run when another run of the same job holds the lock. The job body was not called.
Functions ¶
Types ¶
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard runs jobs under a per-job lock and records every run.
func (*Guard) Handler ¶
Handler serves the health summary: 200 {"status":"ok",...} when every job is healthy, 503 {"status":"degraded",...} otherwise, and 503 {"status":"db_unavailable"} when the table cannot be read. It reports counts only, never job names or errors, so it is safe to expose publicly.
func (*Guard) Migrate ¶
Migrate creates the runs table if it does not exist. It is safe to call from every process at startup.
func (*Guard) Run ¶
func (g *Guard) Run(ctx context.Context, name, schedule string, fn func(context.Context) error) (err error)
Run runs fn under the job's lock and records the outcome. It returns ErrSkipped without calling fn when another run holds the lock, the error from taking the lock if the database is unreachable, and otherwise fn's error. If fn panics, the run is recorded as failed, the lock is released and the panic continues.
schedule is the job's cron expression ("*/5 * * * *", "@every 10m", "@daily", ...). It is stored with the run and used to decide when the job counts as stale.
type Hooks ¶
type Hooks struct {
// Start is called after the lock is taken, right before the job body.
Start func(name, schedule string)
// Finish is called after the run has been recorded. err is the job's
// error, or an error wrapping the panic value if the job panicked.
Finish func(name, schedule string, took time.Duration, err error)
// Skip is called when a run is skipped because the lock is held.
Skip func(name string)
}
Hooks let a service attach its own monitoring, such as cron check-ins in an error tracker. Every field is optional.
type Option ¶
Option configures a Guard.
func WithLockNamespace ¶
WithLockNamespace sets the first key of every advisory lock the Guard takes, keeping its locks apart from other advisory lock users in the same database. Default 7275.
func WithLogger ¶
WithLogger sets where the Guard reports its own problems (a failed lock or record), at most once a minute per job and kind. Default log.Printf.
type Rules ¶
type Rules struct {
// A job is stale once it has gone two of its intervals plus StaleMargin
// without a successful run. Default 10 minutes.
StaleMargin time.Duration
// A job is failing after this many failed runs in a row. Default 3.
FailingAfter int
// A job that has not even started for this long was removed from the
// code and is left out of the health summary. Default 30 days.
RetiredAfter time.Duration
}
Rules decide when a job is unhealthy. Zero fields take the defaults.
type Status ¶
type Status struct {
Name string `json:"name"`
Schedule string `json:"schedule"`
State State `json:"state"`
LastStartedAt time.Time `json:"last_started_at"`
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
LastErrorAt *time.Time `json:"last_error_at,omitempty"`
LastError string `json:"last_error,omitempty"`
LastDuration time.Duration `json:"last_duration_ns"`
ConsecutiveFailures int64 `json:"consecutive_failures"`
Runs int64 `json:"runs"`
SkippedLocked int64 `json:"skipped_locked"`
}
Status is the recorded state of one job.