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

common-patterns.mddocs/guides/getting-started/

Common Patterns

Most frequently used scheduling patterns with gocron v2.

Background Tasks

Regular Cleanup

// Clean up temporary files every hour
j, _ := s.NewJob(
    gocron.DurationJob(time.Hour),
    gocron.NewTask(func() {
        cleanupTempFiles()
    }),
    gocron.WithName("cleanup-temp-files"),
    gocron.WithTags("maintenance", "cleanup"),
)

Cache Refresh

// Refresh cache every 5 minutes
j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        if err := refreshCache(); err != nil {
            log.Printf("Cache refresh failed: %v", err)
        }
    }),
    gocron.WithName("cache-refresh"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Use singleton mode to skip runs if the previous refresh is still in progress.

Health Checks

// Check service health every 30 seconds
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        if err := checkHealth(); err != nil {
            log.Printf("Health check failed: %v", err)
            alerting.SendAlert("Service unhealthy", err.Error())
        }
    }),
    gocron.WithName("health-check"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Time-Based Schedules

Daily Reports

// Generate report every day at 9 AM
j, _ := s.NewJob(
    gocron.DailyJob(
        1,
        gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)),
    ),
    gocron.NewTask(func() {
        report := generateDailyReport()
        sendEmail("admin@example.com", report)
    }),
    gocron.WithName("daily-report"),
)

Business Hours Only

// Run every 15 minutes, Monday-Friday, 9 AM - 5 PM
j, _ := s.NewJob(
    gocron.CronJob("*/15 9-17 * * 1-5", false),
    gocron.NewTask(processBusinessTasks),
    gocron.WithName("business-hours-processor"),
)

Weekend Maintenance

// Run every Saturday at 3 AM
j, _ := s.NewJob(
    gocron.WeeklyJob(
        1,
        gocron.NewWeekdays(time.Saturday),
        gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0)),
    ),
    gocron.NewTask(func() {
        performDatabaseMaintenance()
        optimizeIndexes()
        cleanupOldRecords()
    }),
    gocron.WithName("weekend-maintenance"),
)

Data Processing

Queue Processing

// Process queue items every minute
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        items := fetchQueueItems()
        for _, item := range items {
            if err := processItem(item); err != nil {
                log.Printf("Failed to process item %s: %v", item.ID, err)
            }
        }
    }),
    gocron.WithName("queue-processor"),
    gocron.WithSingletonMode(gocron.LimitModeWait),
)

Use LimitModeWait to ensure all items are processed eventually.

Batch Processing

// Process records in batches every 5 minutes
j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func(ctx context.Context) {
        batchSize := 100
        offset := 0

        for {
            select {
            case <-ctx.Done():
                log.Println("Batch processing cancelled")
                return
            default:
                records := fetchRecords(offset, batchSize)
                if len(records) == 0 {
                    return
                }

                processBatch(records)
                offset += batchSize
            }
        }
    }),
    gocron.WithName("batch-processor"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Database Sync

// Sync with external database every 10 minutes
j, _ := s.NewJob(
    gocron.DurationJob(10*time.Minute),
    gocron.NewTask(func() {
        changes := fetchExternalChanges()
        for _, change := range changes {
            applyChange(change)
        }
        updateSyncTimestamp()
    }),
    gocron.WithName("db-sync"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

API and External Services

Polling

// Poll external API every 2 minutes
j, _ := s.NewJob(
    gocron.DurationJob(2*time.Minute),
    gocron.NewTask(func() {
        data, err := fetchFromAPI()
        if err != nil {
            log.Printf("API poll failed: %v", err)
            return
        }
        processData(data)
    }),
    gocron.WithName("api-poller"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Webhook Retry

// Retry failed webhooks every 5 minutes
j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        failedWebhooks := getFailedWebhooks()
        for _, webhook := range failedWebhooks {
            if err := retryWebhook(webhook); err != nil {
                log.Printf("Webhook retry failed: %v", err)
            } else {
                markWebhookSuccessful(webhook)
            }
        }
    }),
    gocron.WithName("webhook-retry"),
)

Rate-Limited Requests

// Process rate-limited requests
j, _ := s.NewJob(
    gocron.DurationJob(time.Second),
    gocron.NewTask(func() {
        // Process one request per second to respect rate limit
        request := getNextPendingRequest()
        if request != nil {
            sendRequest(request)
        }
    }),
    gocron.WithName("rate-limited-processor"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Monitoring and Metrics

Metrics Collection

// Collect system metrics every 10 seconds
j, _ := s.NewJob(
    gocron.DurationJob(10*time.Second),
    gocron.NewTask(func() {
        metrics := collectSystemMetrics()
        sendToMonitoring(metrics)
    }),
    gocron.WithName("metrics-collector"),
)

Log Aggregation

// Aggregate logs every minute
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() {
        logs := aggregateLogs()
        if len(logs) > 0 {
            sendToLogService(logs)
        }
    }),
    gocron.WithName("log-aggregator"),
    gocron.WithSingletonMode(gocron.LimitModeWait),
)

Initialization and Warmup

Startup Tasks

// Run warmup immediately on startup
j, _ := s.NewJob(
    gocron.DurationJob(24*time.Hour),
    gocron.NewTask(func() {
        warmupCache()
        preloadData()
    }),
    gocron.WithName("warmup"),
    gocron.WithStartAt(gocron.WithStartImmediately()),
)

One-Time Delayed Task

// Send reminder in 1 hour
reminderTime := time.Now().Add(time.Hour)
j, _ := s.NewJob(
    gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(reminderTime)),
    gocron.NewTask(func() {
        sendReminder("Don't forget to submit your report!")
    }),
    gocron.WithName("reminder"),
)

Multiple Schedulers

Separate High/Low Priority

// High-priority scheduler (no concurrency limit)
highPriority, _ := gocron.NewScheduler()
defer highPriority.Shutdown()

highPriority.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(criticalTask),
    gocron.WithName("critical"),
)

// Low-priority scheduler (limited to 2 concurrent jobs)
lowPriority, _ := gocron.NewScheduler(
    gocron.WithLimitConcurrentJobs(2, gocron.LimitModeReschedule),
)
defer lowPriority.Shutdown()

lowPriority.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(backgroundTask),
    gocron.WithName("background"),
)

highPriority.Start()
lowPriority.Start()

Error Handling

With Retry Logic

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() error {
        maxRetries := 3
        for attempt := 1; attempt <= maxRetries; attempt++ {
            if err := performOperation(); err != nil {
                if attempt == maxRetries {
                    log.Printf("Operation failed after %d attempts", maxRetries)
                    return err
                }
                log.Printf("Attempt %d failed, retrying...", attempt)
                time.Sleep(time.Second * time.Duration(attempt))
                continue
            }
            return nil
        }
        return nil
    }),
    gocron.WithName("operation-with-retry"),
)

With Event Listeners

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(riskyOperation),
    gocron.WithName("risky-operation"),
    gocron.WithEventListeners(
        gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
            log.Printf("Job %s failed: %v", jobName, err)
            sendAlert(jobName, err)
        }),
        gocron.AfterJobRunsWithPanic(func(jobID uuid.UUID, jobName string, recoverData any) {
            log.Printf("Job %s panicked: %v", jobName, recoverData)
            sendCriticalAlert(jobName, recoverData)
        }),
    ),
)

Context-Aware Jobs

Graceful Cancellation

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func(ctx context.Context) {
        ticker := time.NewTicker(5 * time.Second)
        defer ticker.Stop()

        for {
            select {
            case <-ctx.Done():
                log.Println("Job cancelled, cleaning up...")
                cleanup()
                return
            case <-ticker.C:
                doWork()
            }
        }
    }),
    gocron.WithName("long-running-job"),
)

With Timeout

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func(ctx context.Context) {
        select {
        case <-ctx.Done():
            log.Println("Job timed out")
            return
        default:
            doWork()
        }
    }),
    gocron.WithName("timed-job"),
    gocron.WithContext(ctx),
)

Dynamic Job Management

Add Jobs Conditionally

func setupJobs(s gocron.Scheduler, config Config) {
    // Always add core jobs
    s.NewJob(
        gocron.DurationJob(time.Minute),
        gocron.NewTask(coreTask),
        gocron.WithName("core-task"),
    )

    // Conditionally add feature jobs
    if config.EnableReporting {
        s.NewJob(
            gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0))),
            gocron.NewTask(generateReport),
            gocron.WithName("daily-report"),
        )
    }

    if config.EnableCleanup {
        s.NewJob(
            gocron.DurationJob(time.Hour),
            gocron.NewTask(cleanup),
            gocron.WithName("cleanup"),
        )
    }
}

Remove Jobs by Tag

// Add temporary jobs
s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(tempTask1),
    gocron.WithTags("temporary", "experiment"),
)

s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(tempTask2),
    gocron.WithTags("temporary", "experiment"),
)

// Later: remove all temporary jobs
s.RemoveByTags("temporary")

Best Practices Summary

1. Always Use Job Names

// Good: named job
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithName("my-cleanup-job"),
)

// Bad: unnamed job (hard to identify in logs)
j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
)

2. Use Singleton Mode for Long-Running Jobs

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

3. Handle Context Cancellation

task := gocron.NewTask(func(ctx context.Context) {
    select {
    case <-ctx.Done():
        return
    default:
        doWork()
    }
})

4. Always Defer Shutdown

s, _ := gocron.NewScheduler()
defer s.Shutdown()

5. Use Tags for Job Management

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithTags("cleanup", "database", "production"),
)

Related Documentation

  • Quick Start — Getting started guide
  • Creating Jobs — All job definition types
  • Managing Jobs — Update and remove jobs
  • Concurrency Control — Prevent overlapping executions
  • Advanced Patterns — Production-ready patterns

Install with Tessl CLI

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

docs

index.md

tile.json