CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com-go-co-op-gocron-v2

A Golang job scheduling library that lets you run Go functions at pre-determined intervals using cron expressions, fixed durations, daily, weekly, monthly, or one-time schedules with support for distributed deployments.

Overview
Eval results
Files

distributed.mddocs/api/options/

Distributed Options

Options for distributed job coordination.

WithDistributedJobLocker

func WithDistributedJobLocker(locker Locker) JobOption

Sets per-job distributed locker, overriding global locker.

Errors:

  • ErrWithDistributedJobLockerNil: locker is nil

See Locker interface.

// Scheduler has global locker
s, _ := gocron.NewScheduler(
    gocron.WithDistributedLocker(globalLocker),
)

// This job uses different locker
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithName("special-job"),
    gocron.WithDistributedJobLocker(specialLocker),
)

Uses: Different lock backends per job, different TTLs, testing

WithDisabledDistributedJobLocker

func WithDisabledDistributedJobLocker(disabled bool) JobOption

Disables global distributed locker for specific job.

// Scheduler has global locker
s, _ := gocron.NewScheduler(
    gocron.WithDistributedLocker(globalLocker),
)

// This job skips locking
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(localOnlyFunc),
    gocron.WithDisabledDistributedJobLocker(true),
)

Uses: Local-only jobs (node-specific health checks), jobs that don't need coordination, testing

Global Distributed Options

Set at scheduler level:

WithDistributedElector

func WithDistributedElector(elector Elector) SchedulerOption

Enables leader election. Only the leader instance runs jobs.

See Elector interface and Creating Schedulers.

WithDistributedLocker

func WithDistributedLocker(locker Locker) SchedulerOption

Enables per-job distributed locking for all jobs.

See Locker interface and Creating Schedulers.

Usage Examples

Per-Job Locker

// Global Redis locker
redisLocker := &RedisLocker{client: redisClient}

s, _ := gocron.NewScheduler(
    gocron.WithDistributedLocker(redisLocker),
)

// Most jobs use Redis
j1, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(normalTask),
)

// Critical job uses etcd locker
etcdLocker := &EtcdLocker{client: etcdClient}
j2, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(criticalTask),
    gocron.WithName("critical-job"),
    gocron.WithDistributedJobLocker(etcdLocker),
)

Disable Locking for Local Jobs

s, _ := gocron.NewScheduler(
    gocron.WithDistributedLocker(globalLocker),
)

// This job needs coordination
j1, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        processSharedQueue()
    }),
    gocron.WithName("queue-processor"),
)

// This job is node-specific, no locking needed
j2, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        checkLocalDiskSpace()
    }),
    gocron.WithName("disk-check"),
    gocron.WithDisabledDistributedJobLocker(true),
)

Mixed Deployment

s, _ := gocron.NewScheduler(
    gocron.WithDistributedElector(elector), // Only leader runs jobs
    gocron.WithDistributedLocker(locker),   // Leader uses locks for safety
)

// Coordinated job (runs on leader only, with lock)
j1, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(coordinatedTask),
    gocron.WithName("coordinated"),
)

// Node-local job (runs on all nodes, no election/locking)
j2, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(localMetrics),
    gocron.WithName("local-metrics"),
    gocron.WithDisabledDistributedJobLocker(true),
)

Lock Error Handling

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithName("locked-job"),
    gocron.WithEventListeners(
        gocron.AfterLockError(func(jobID uuid.UUID, jobName string, err error) {
            log.Printf("Lock acquisition failed for %s: %v", jobName, err)
            metricsLockContentions.WithLabelValues(jobName).Inc()
        }),
    ),
)

Lock Behavior

Global Locker

When set via WithDistributedLocker:

  • All jobs attempt to acquire lock before running
  • Lock key is the job's name (from WithName)
  • If lock fails, job is skipped for this run
  • AfterLockError listener is called on failure

Per-Job Locker

When set via WithDistributedJobLocker:

  • Overrides global locker for this job only
  • Same behavior as global locker
  • Useful for different lock backends or TTLs

Disabled Locker

When set via WithDisabledDistributedJobLocker:

  • Job runs without attempting lock
  • Ignores global locker setting
  • Job runs on all instances (if no elector)

Interaction with Election

If both elector and locker are set:

  1. Elector determines which instance can run jobs
  2. Only leader instance attempts to acquire locks
  3. Locks provide additional safety (e.g., during leader transitions)
s, _ := gocron.NewScheduler(
    gocron.WithDistributedElector(elector), // Only leader runs
    gocron.WithDistributedLocker(locker),   // Leader still uses locks
)

Testing Without Distribution

Disable distribution for local testing:

var s gocron.Scheduler

if isProduction {
    s, _ = gocron.NewScheduler(
        gocron.WithDistributedElector(elector),
        gocron.WithDistributedLocker(locker),
    )
} else {
    s, _ = gocron.NewScheduler()
}

// Jobs work in both modes
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithName("my-job"),
)

Best Practices

  1. Use stable job names: Lock keys are derived from names
  2. Set appropriate TTLs: Lock timeout should exceed max job duration
  3. Handle lock errors: Use AfterLockError listener
  4. Test lock failures: Ensure graceful degradation
  5. Monitor lock contention: Track AfterLockError frequency
  6. Consider lock cost: Disable for high-frequency local jobs

See Also

Install with Tessl CLI

npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1

docs

api

errors.md

quick-reference.md

tasks.md

index.md

tile.json