Help us shape the future of Go! Take the 2026 Go Developer Survey

cron

package
v1.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package cron provides distributed cron scheduling with leader election and per-tenant support.

Cron entries are stored in the database and fired only by the cluster leader. This guarantees at-most-once firing even when multiple Dispatch instances are running.

Entry

An Entry represents a recurring job schedule:

  • Schedule: standard cron expression (e.g., "0 9 * * 1-5")
  • JobName: the registered job definition to enqueue when fired
  • Queue: target queue (defaults to "default")
  • Payload: static JSON payload passed to every triggered job
  • ScopeAppID / ScopeOrgID: tenant scoping
  • Enabled: whether the entry fires
  • LockedBy / LockedUntil: distributed lock fields (managed internally)

Registering a Cron

Use engine.RegisterCron to add a cron entry at startup:

engine.RegisterCron(ctx, eng, "daily-report", "0 9 * * *",
    GenerateReport, ReportInput{Format: "pdf"})

Enable / Disable

Cron entries can be enabled or disabled at runtime via the admin API (POST /v1/crons/:cronId/enable and POST /v1/crons/:cronId/disable). Operator code goes through the engine's EnableCron, DisableCron, DeleteCron and TriggerCron. Enabling computes the next fire time from now, so an entry that was off past its old one does not fire a catch-up. NextFires previews a schedule's next fire times.

Scheduler

The Scheduler evaluates due entries on every tick, acquires a distributed lock on each entry, re-reads it, and fires only if the store still has it enabled and due. It enqueues the corresponding job and records LastRunAt and NextRunAt with targeted writes that never touch Enabled, so a fire cannot undo an operator's disable. The [ext.CronFired] extension hook fires after each enqueue.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NextFires

func NextFires(schedule string, from time.Time, n int) ([]time.Time, *time.Location, error)

NextFires returns the next n times schedule fires strictly after from, and the location the schedule is evaluated in.

It parses with ParseSchedule, so it accepts exactly what the scheduler and engine.RegisterCron accept, and it steps the schedule the way the scheduler does, from a UTC instant. A schedule with no CRON_TZ= or TZ= prefix therefore runs in UTC whatever the process's local zone is, and a prefixed one runs in the zone it names. The returned times are in that location, so their wall clock reads as the schedule does.

Fewer than n times come back when the schedule runs out. The parser searches five years ahead and then gives up, so "0 0 30 2 *" (30 February) yields none and no error. n must be positive.

func ParseSchedule

func ParseSchedule(expr string) (cronlib.Schedule, error)

ParseSchedule parses a cron expression and returns the schedule. Exported for use by engine.RegisterCron.

Types

type Definition

type Definition[T any] struct {
	// Name is the unique identifier for this cron entry.
	Name string

	// Schedule is a cron expression (e.g., "*/5 * * * *" or "@every 30s").
	Schedule string

	// JobName is the name of the job to enqueue on each tick.
	JobName string

	// Payload is the default payload to enqueue with the job.
	Payload T

	// Queue overrides the default job queue (optional).
	Queue string
}

Definition is a typed cron definition. T is the payload type (must be JSON-serializable).

type Emitter

type Emitter interface {
	EmitCronFired(ctx context.Context, entryName string, jobID id.JobID)
}

Emitter emits cron lifecycle events. ext.Registry satisfies this interface via EmitCronFired.

type EnqueueFunc

type EnqueueFunc func(ctx context.Context, name string, payload []byte, opts ...job.Option) (id.JobID, error)

EnqueueFunc is the callback the scheduler uses to enqueue jobs. This breaks the import cycle: the engine provides the implementation.

type Entry

type Entry struct {
	dispatch.Entity

	ID          id.CronID  `json:"id"`
	Name        string     `json:"name"`
	Schedule    string     `json:"schedule"`
	JobName     string     `json:"job_name"`
	Queue       string     `json:"queue,omitempty"`
	Payload     []byte     `json:"payload,omitempty"`
	ScopeAppID  string     `json:"scope_app_id,omitempty"`
	ScopeOrgID  string     `json:"scope_org_id,omitempty"`
	LastRunAt   *time.Time `json:"last_run_at,omitempty"`
	NextRunAt   *time.Time `json:"next_run_at,omitempty"`
	LockedBy    string     `json:"locked_by,omitempty"`
	LockedUntil *time.Time `json:"locked_until,omitempty"`
	Enabled     bool       `json:"enabled"`
}

Entry represents a scheduled cron job.

type Scheduler

type Scheduler struct {
	// contains filtered or unexported fields
}

Scheduler runs cron entries on a tick loop. Only the cluster leader executes cron ticks to prevent double-firing.

func NewScheduler

func NewScheduler(
	cronStore Store,
	clusterStore cluster.Store,
	enqueue EnqueueFunc,
	emitter Emitter,
	workerID id.WorkerID,
	logger log.Logger,
	opts ...SchedulerOption,
) *Scheduler

NewScheduler creates a Scheduler.

func (*Scheduler) InvalidateCronCache

func (s *Scheduler) InvalidateCronCache()

InvalidateCronCache forces the scheduler to re-list cron entries from the store on the next tick. Call it after registering, updating, or removing entries so changes take effect before the next scheduled refresh.

func (*Scheduler) Settings

func (s *Scheduler) Settings() Settings

Settings reports the effective scheduler timings.

func (*Scheduler) Start

func (s *Scheduler) Start(_ context.Context) error

Start launches the leader election and cron tick goroutines.

func (*Scheduler) Stop

func (s *Scheduler) Stop(_ context.Context) error

Stop signals the scheduler to stop and waits for goroutines to finish. It is safe to call Stop multiple times; only the first call closes the stop channel and waits for goroutines.

type SchedulerOption

type SchedulerOption func(*Scheduler)

SchedulerOption configures a Scheduler.

func WithCronRefreshInterval

func WithCronRefreshInterval(d time.Duration) SchedulerOption

WithCronRefreshInterval sets how often the leader re-lists cron entries from the store. Between refreshes ticks run against an in-memory cache; InvalidateCronCache forces an early refresh. Default 30s.

func WithLeaderTTL

func WithLeaderTTL(d time.Duration) SchedulerOption

WithLeaderTTL sets the TTL for leader election.

func WithLockTTL

func WithLockTTL(d time.Duration) SchedulerOption

WithLockTTL sets the TTL for per-entry distributed locks.

func WithSchedulerStoreCallTimeout

func WithSchedulerStoreCallTimeout(d time.Duration) SchedulerOption

WithSchedulerStoreCallTimeout caps a single store roundtrip. Pass a positive duration to override defaultStoreCallTimeout, zero to keep the default, or negative to disable the timeout (test-only).

func WithTelemetry

func WithTelemetry(t *telemetry.Telemetry) SchedulerOption

WithTelemetry sets where the scheduler records its spans and metrics: one root "fire cron entry" span per due entry, the fire counter and lag histogram, and the leadership gauge. engine.Build passes the engine's own.

func WithTickInterval

func WithTickInterval(d time.Duration) SchedulerOption

WithTickInterval sets how often the scheduler checks for due entries.

type Settings

type Settings struct {
	TickInterval, LeaderTTL, RefreshInterval, LockTTL, StoreCallTimeout time.Duration
	StoreCallsBounded                                                   bool
}

Settings is the configuration of this process's scheduler.

type Store

type Store interface {
	TargetedUpdater

	// RegisterCron persists a new cron entry. Returns an error if the name
	// already exists.
	RegisterCron(ctx context.Context, entry *Entry) error

	// GetCron retrieves a cron entry by ID.
	GetCron(ctx context.Context, entryID id.CronID) (*Entry, error)

	// ListCrons returns all cron entries.
	ListCrons(ctx context.Context) ([]*Entry, error)

	// AcquireCronLock attempts to acquire a distributed lock for a cron entry.
	// Returns true if the lock was acquired. The lock expires after ttl.
	AcquireCronLock(ctx context.Context, entryID id.CronID, workerID id.WorkerID, ttl time.Duration) (bool, error)

	// ReleaseCronLock releases the distributed lock for a cron entry.
	ReleaseCronLock(ctx context.Context, entryID id.CronID, workerID id.WorkerID) error

	// UpdateCronLastRun records when a cron entry last fired.
	UpdateCronLastRun(ctx context.Context, entryID id.CronID, at time.Time) error

	// UpdateCronEntry updates a cron entry (Enabled, NextRunAt, etc.).
	UpdateCronEntry(ctx context.Context, entry *Entry) error

	// DeleteCron removes a cron entry by ID.
	DeleteCron(ctx context.Context, entryID id.CronID) error
}

Store defines the persistence contract for cron entries.

type TargetedUpdater

type TargetedUpdater interface {
	// SetCronEnabled sets enabled, sets next_run_at when nextRunAt is
	// non-nil (nil leaves it unchanged), and stamps updated_at. It
	// returns dispatch.ErrCronNotFound when the entry does not exist.
	SetCronEnabled(ctx context.Context, entryID id.CronID, enabled bool, nextRunAt *time.Time) error

	// UpdateCronNextRun sets next_run_at and stamps updated_at, and
	// nothing else; in particular it never touches enabled. It returns
	// dispatch.ErrCronNotFound when the entry does not exist.
	UpdateCronNextRun(ctx context.Context, entryID id.CronID, nextRunAt time.Time) error
}

TargetedUpdater writes single fields so a scheduler fire can never overwrite an operator's enable or disable. The whole-row UpdateCronEntry write-back is what let a disabled cron come back: the scheduler read the entry while it was enabled, an operator disabled it, and the scheduler's post-fire write put the old row, enabled flag and all, back on top.

Each method changes only the columns it names. Every other field, including the lock and last_run_at, reads back exactly as it was.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL