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

troubleshooting.mddocs/guides/observability/

Troubleshooting

Common issues and solutions for gocron.

Jobs Not Running

Symptom

Jobs are scheduled but never execute.

Possible Causes

1. Scheduler Not Started

s, _ := gocron.NewScheduler()
j, _ := s.NewJob(...)
// Missing: s.Start()

Solution: Call Start():

s.Start()

2. Application Exits Before Execution

s, _ := gocron.NewScheduler()
j, _ := s.NewJob(...)
s.Start()
// Application exits immediately

Solution: Block main goroutine:

s.Start()
select {} // block forever

Or use proper shutdown:

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

s.Start()
<-ctx.Done()
s.Shutdown()

3. Singleton Mode Blocking

j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        time.Sleep(2*time.Minute) // Takes longer than interval
    }),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Solution: Increase interval or remove singleton mode:

// Option 1: Increase interval
gocron.DurationJob(3*time.Minute)

// Option 2: Use Wait mode
gocron.WithSingletonMode(gocron.LimitModeWait)

Incorrect Execution Times

Symptom

Jobs run at unexpected times.

Possible Causes

1. Timezone Issues

// Local time vs UTC confusion
j, _ := s.NewJob(
    gocron.CronJob("0 9 * * *", false), // 9 AM in what timezone?
    gocron.NewTask(dailyReport),
)

Solution: Explicitly set timezone:

loc, _ := time.LoadLocation("America/New_York")
s, _ := gocron.NewScheduler(
    gocron.WithLocation(loc),
)

Or use cron timezone:

j, _ := s.NewJob(
    gocron.CronJob("TZ=America/New_York 0 9 * * *", false),
    gocron.NewTask(dailyReport),
)

2. Cron Expression Errors

// Incorrect: runs every minute
j, _ := s.NewJob(
    gocron.CronJob("* * * * *", false),
    gocron.NewTask(hourlyTask),
)

Solution: Verify cron expression:

// Correct: runs at minute 0 of every hour
j, _ := s.NewJob(
    gocron.CronJob("0 * * * *", false),
    gocron.NewTask(hourlyTask),
)

3. Start Time Offset

// Job won't run until start time
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithStartAt(
        gocron.WithStartDateTime(time.Now().Add(time.Hour)),
    ),
)

High Memory Usage

Symptom

Memory usage grows over time.

Possible Causes

1. Queue Buildup with LimitModeWait

j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        time.Sleep(2*time.Minute) // Slow job
    }),
    gocron.WithSingletonMode(gocron.LimitModeWait),
)

Solution: Monitor and adjust:

// Monitor queue
go func() {
    ticker := time.NewTicker(time.Minute)
    for range ticker.C {
        waiting := s.JobsWaitingInQueue()
        if waiting > 100 {
            log.Printf("WARNING: Queue growing: %d jobs", waiting)
        }
    }
}()

// Switch to Reschedule mode
gocron.WithSingletonMode(gocron.LimitModeReschedule)

2. Goroutine Leaks in Job Functions

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        go func() {
            // Goroutine never exits
            for {
                doWork()
            }
        }()
    }),
)

Solution: Use context for cleanup:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func(ctx context.Context) {
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return
                default:
                    doWork()
                }
            }
        }()
    }),
)

Jobs Running Concurrently

Symptom

Multiple instances of same job run simultaneously.

Cause

No concurrency control configured.

Solution

Use singleton mode:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Distributed Issues

Jobs Run on Multiple Instances

Cause: Distributed locking not configured.

Solution: Use leader election or distributed locking:

// Option 1: Leader election
s, _ := gocron.NewScheduler(
    gocron.WithDistributedElector(myElector),
)

// Option 2: Distributed locking
s, _ := gocron.NewScheduler(
    gocron.WithDistributedLocker(myLocker),
)

Time Drift Between Instances

Cause: Clocks not synchronized.

Solution: Use NTP:

# Install and configure NTP
sudo apt-get install ntp
sudo systemctl enable ntp
sudo systemctl start ntp

Performance Issues

Slow Job Execution

Symptom: Jobs take longer than expected.

Debug:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        start := time.Now()
        defer func() {
            log.Printf("Execution took: %v", time.Since(start))
        }()

        doWork()
    }),
)

Too Many Concurrent Jobs

Symptom: System overload.

Solution: Limit concurrency:

s, _ := gocron.NewScheduler(
    gocron.WithLimitConcurrentJobs(10, gocron.LimitModeReschedule),
)

Error Handling

Errors Not Caught

// Bad: panic not recovered
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        panic("something went wrong")
    }),
)

Solution: Use error returns:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() error {
        if err := doWork(); err != nil {
            return fmt.Errorf("work failed: %w", err)
        }
        return nil
    }),
    gocron.WithEventListeners(
        gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
            log.Printf("Job failed: %v", err)
        }),
    ),
)

Graceful Shutdown Issues

Jobs Interrupted

Cause: No shutdown timeout configured.

Solution: Set timeout:

s, _ := gocron.NewScheduler(
    gocron.WithStopTimeout(30*time.Second),
)
defer s.Shutdown()

Jobs Don't Respect Shutdown

Cause: Jobs don't check context.

Solution: Use context:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                log.Println("Shutting down gracefully...")
                cleanup()
                return
            default:
                doWork()
            }
        }
    }),
)

Debugging Tips

Enable Debug Logging

type debugLogger struct{}

func (l *debugLogger) Debug(msg string, args ...any) {
    log.Printf("[DEBUG] "+msg, args...)
}
// ... implement other methods

s, _ := gocron.NewScheduler(
    gocron.WithLogger(&debugLogger{}),
)

Monitor with SchedulerMonitor

type debugMonitor struct{}

func (m *debugMonitor) JobScheduled(jobID uuid.UUID, job gocron.Job) {
    log.Printf("Scheduled: %s next run: %v", job.Name(), job.NextRun())
}

func (m *debugMonitor) JobStarted(jobID uuid.UUID, job gocron.Job) {
    log.Printf("Started: %s", job.Name())
}

func (m *debugMonitor) JobCompleted(jobID uuid.UUID, job gocron.Job, err error) {
    log.Printf("Completed: %s error: %v", job.Name(), err)
}

// ... implement other methods

s, _ := gocron.NewScheduler(
    gocron.WithSchedulerMonitor(&debugMonitor{}),
)

Check Job Status

jobs := s.Jobs()
for _, j := range jobs {
    log.Printf("Job: %s", j.Name())
    log.Printf("  Next run: %v", j.NextRun())
    log.Printf("  Last run: %v", j.LastRun())
}

Common Error Messages

"ErrCronJobParse"

Invalid cron expression.

Solution: Verify cron syntax:

// Bad: invalid expression
gocron.CronJob("* * * *", false) // Only 4 fields

// Good: valid expression
gocron.CronJob("* * * * *", false) // 5 fields

"context canceled"

Job cancelled during execution.

Cause: Scheduler shutdown or job removed.

Solution: Handle gracefully:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func(ctx context.Context) error {
        select {
        case <-ctx.Done():
            return ctx.Err() // Return context error
        default:
            return doWork()
        }
    }),
)

See Also

  • Observability Guide - Monitoring and debugging
  • Logging Guide - Configure logging
  • Lifecycle Guide - Job lifecycle
  • API: Errors - Error reference

Install with Tessl CLI

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

docs

index.md

tile.json