scheduler

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package scheduler provides a distributed task scheduler with distributed locking and task dispatch.

Unlike a simple cron library (which only parses expressions and fires callbacks in a single process), this package ensures that scheduled jobs run on exactly one node in a cluster — even when multiple instances of the same application are running simultaneously.

Key features

  • Cron expression scheduling (via common/cron) and interval scheduling.
  • Distributed lock integration (via the lock package): only the node that acquires the lock for a job executes it.
  • Pluggable LockFactory: use Redis, etcd, Zookeeper, or the in-memory lock for single-node / testing.
  • Context-aware job execution with timeout.
  • Job metadata: name, description, tags, singleton mode.
  • Graceful shutdown: stop accepting new ticks, wait for running jobs.
  • Job status tracking: last run, next run, error count.
  • Optional job event listener for monitoring/metrics.

Architecture

The Scheduler runs a goroutine per job. Each goroutine calculates the next fire time from the cron expression (or interval), sleeps until that time, then attempts to acquire a distributed lock for the job. If the lock is acquired, the job function runs. If not (another node got it), the job is skipped and the goroutine waits for the next fire time.

This design is deliberately simple and avoids external dependencies on message queues or coordination services beyond the lock backend.

Quick start (single node)

// In-memory lock — single node only.
lockMgr := memory.NewManager()
s := scheduler.New(scheduler.Config{
    LockFactory: scheduler.LockFactoryFunc(func(jobName string) (lock.Locker, error) {
        return lockMgr.NewMutex("scheduler:"+jobName, lock.WithTTL(30*time.Second))
    }),
})
s.Start()

s.Add("cleanup", "*/5 * * * *", func(ctx context.Context) error {
    return cleanupDatabase(ctx)
})

// Graceful shutdown.
s.Stop()

Quick start (distributed, Redis)

rdb := redis.NewClient(...)
s := scheduler.New(scheduler.Config{
    LockFactory: scheduler.LockFactoryFunc(func(jobName string) (lock.Locker, error) {
        return redis.NewMutex(rdb, "scheduler:"+jobName,
            lock.WithTTL(60*time.Second),
            lock.WithRetryDelay(200*time.Millisecond))
    }),
    LockTTL: 60 * time.Second,
})
s.Start()
s.Add("report", "0 * * * *", generateReport)
defer s.Stop()

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrSchedulerStopped is returned when operating on a stopped scheduler.
	ErrSchedulerStopped = errors.New("scheduler: stopped")
	// ErrJobNotFound is returned when a job with the given name does not exist.
	ErrJobNotFound = errors.New("scheduler: job not found")
	// ErrJobExists is returned when adding a job with a name that already exists.
	ErrJobExists = errors.New("scheduler: job already exists")
	// ErrInvalidSchedule is returned when the schedule expression is invalid.
	ErrInvalidSchedule = errors.New("scheduler: invalid schedule")
	// ErrNoLockFactory is returned when no LockFactory is configured.
	ErrNoLockFactory = errors.New("scheduler: no lock factory configured")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// LockFactory creates distributed locks for jobs. Required for distributed
	// mode. Use NoLockFactory{} for single-node mode (no dispatch).
	LockFactory LockFactory

	// LockTTL is the TTL for the distributed lock. Default: 30s.
	// The lock is refreshed periodically while the job runs (if the lock
	// backend supports Refresh).
	LockTTL time.Duration

	// LockRefreshInterval is how often the lock is refreshed while a job
	// runs. Default: LockTTL / 3.
	LockRefreshInterval time.Duration

	// Timezone is the timezone for cron expression evaluation.
	// Default: time.Local.
	Timezone *time.Location

	// EventListener receives job events. Optional.
	EventListener EventListener
}

Config configures the scheduler.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with sensible defaults.

type EventListener

type EventListener interface {
	OnJobEvent(event JobEvent)
}

EventListener receives job events. Use this for monitoring, metrics, or logging.

type EventListenerFunc

type EventListenerFunc func(JobEvent)

EventListenerFunc is a function adapter for EventListener.

func (EventListenerFunc) OnJobEvent

func (f EventListenerFunc) OnJobEvent(e JobEvent)

OnJobEvent implements EventListener.

type Job

type Job struct {
	Name        string        // unique job identifier
	Description string        // human-readable description
	Schedule    string        // cron expression or "@every <duration>"
	Func        JobFunc       // the function to execute
	Timeout     time.Duration // per-execution timeout (0 = no timeout)
	Singleton   bool          // if true, skip if previous run is still active
	Tags        []string      // optional tags for grouping/filtering
	// contains filtered or unexported fields
}

Job represents a scheduled task.

func (*Job) Status

func (j *Job) Status() JobStatus

Status returns a snapshot of the job's runtime status.

type JobEvent

type JobEvent struct {
	JobName  string
	Type     JobEventType
	Time     time.Time
	Error    error
	Duration time.Duration
}

JobEvent represents an event in a job's lifecycle.

type JobEventType

type JobEventType int

JobEventType describes the type of job event.

const (
	// EventJobStarted is emitted when a job starts executing.
	EventJobStarted JobEventType = iota
	// EventJobSucceeded is emitted when a job completes successfully.
	EventJobSucceeded
	// EventJobFailed is emitted when a job returns an error.
	EventJobFailed
	// EventJobSkipped is emitted when a job is skipped (lock not acquired
	// or singleton mode blocked it).
	EventJobSkipped
	// EventJobAdded is emitted when a job is added to the scheduler.
	EventJobAdded
	// EventJobRemoved is emitted when a job is removed from the scheduler.
	EventJobRemoved
)

type JobFunc

type JobFunc func(ctx context.Context) error

JobFunc is the function executed when a job fires. It receives a context that is cancelled when the job timeout expires or when the scheduler is shutting down.

type JobStatus

type JobStatus struct {
	LastRun     time.Time
	LastEnd     time.Time
	NextRun     time.Time
	LastError   error
	LastErrorAt time.Time
	RunCount    int64
	ErrorCount  int64
	Running     bool
}

JobStatus holds the runtime status of a job.

type LockFactory

type LockFactory interface {
	// NewLock returns a Locker for the given job name. The returned Locker
	// will be used with TryLock (non-blocking) by the scheduler.
	NewLock(jobName string) (lock.Locker, error)
}

LockFactory creates a distributed lock for a given job name. Each job gets its own lock key so that different jobs can run in parallel on different nodes, while the same job runs on only one node at a time.

Implementations can wrap the lock/redis, lock/etcd, lock/memory, etc. backends.

type LockFactoryFunc

type LockFactoryFunc func(jobName string) (lock.Locker, error)

LockFactoryFunc is a function adapter for LockFactory.

func (LockFactoryFunc) NewLock

func (f LockFactoryFunc) NewLock(jobName string) (lock.Locker, error)

NewLock implements LockFactory.

type NoLockFactory

type NoLockFactory struct{}

NoLockFactory is a LockFactory that returns nil locks. Use this for single-node scheduling where distributed locking is not needed. In this mode, jobs always run on every node (no dispatch).

func (NoLockFactory) NewLock

func (NoLockFactory) NewLock(string) (lock.Locker, error)

NewLock returns nil (no locking).

type Options

type Options struct {
	Description string
	Timeout     time.Duration
	Singleton   bool
	Tags        []string
}

Options configures a job's behavior.

type Scheduler

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

Scheduler manages a set of scheduled jobs with distributed locking.

func New

func New(cfg Config) *Scheduler

New creates a new Scheduler. The scheduler is not started; call Start.

func (*Scheduler) Add

func (s *Scheduler) Add(name, schedule string, fn JobFunc) error

Add registers a new job. If the scheduler is already started, the job begins running immediately.

func (*Scheduler) AddWithOptions

func (s *Scheduler) AddWithOptions(name, schedule string, fn JobFunc, opts Options) error

AddWithOptions registers a new job with additional options.

func (*Scheduler) Get

func (s *Scheduler) Get(name string) (*Job, error)

Get returns a job by name.

func (*Scheduler) JobCount

func (s *Scheduler) JobCount() int

JobCount returns the number of registered jobs.

func (*Scheduler) Jobs

func (s *Scheduler) Jobs() []*Job

Jobs returns a snapshot of all registered jobs.

func (*Scheduler) Remove

func (s *Scheduler) Remove(name string) error

Remove unregisters a job. The job's goroutine is signaled to stop.

func (*Scheduler) Start

func (s *Scheduler) Start()

Start begins the scheduler. Jobs added before Start will begin running immediately; jobs added after Start will be picked up dynamically.

func (*Scheduler) Stop

func (s *Scheduler) Stop()

Stop gracefully stops the scheduler. It signals all job goroutines to stop, waits for running jobs to finish (up to their timeout), and returns.

Jump to

Keyboard shortcuts

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