How to Set Up Cron Jobs Using the Cronx Module in Gorig

Use the cronx package in jom-io/gorig to register scheduled tasks via AddCronTask, AddEveryTask, or AddOnceTask, then start the scheduler with Startup and stop it gracefully with Shutdown.

The cronx module provides Gorig’s official abstraction for background job scheduling, wrapping the robust robfig/cron library with framework-specific lifecycle management. When you set up cron jobs using the cronx module in Gorig, you gain built-in context propagation, panic recovery, timeout enforcement, and graceful shutdown handling that integrates seamlessly with the rest of the Gorig ecosystem.

Understanding the Cronx Module Architecture

The cronx package is a thin but opinionated wrapper located in cronx/cron.go. It manages a singleton cron.Cron instance and exposes registration helpers that enforce Gorig’s standards for observability and safety.

Key architectural features include:

  • Context-aware execution – Every task receives a context.Context derived from the job’s timeout or cancellation signal.
  • Panic recovery – The wrapper in cronx/task.go automatically recovers from panics, logs the stack trace, and prevents the scheduler from crashing.
  • Timeout enforcement – Optional timeout parameters create cancellable goroutines that respect deadlines.
  • Lifecycle hooks – Startup and Shutdown methods follow Gorig’s standard service pattern, enabling clean initialization and termination.

Registering Cron Jobs in Gorig

All task registration must occur before calling Startup. The module offers four primary registration functions, each defined in cronx/cron.go.

Recurring Tasks with Cron Expressions

Use AddCronTask when you need standard cron syntax (@every 1s, 0 0 * * *, etc.).

// Runs every 5 minutes with a 30-second timeout
cronx.AddCronTask("*/5 * * * *", func(ctx context.Context) {
    // Your business logic here
    processData(ctx)
}, 30*time.Second)

Interval-Based Execution

AddEveryTask provides a more readable API for fixed intervals without writing cron expressions.

// Executes every 10 seconds
cronx.AddEveryTask(10*time.Second, func(ctx context.Context) {
    cleanupTempFiles(ctx)
}, 5*time.Second) // 5s timeout

Delayed and One-Off Tasks

For non-recurring workloads, use AddDelayTask or AddOnceTask.

// Run once after a 1-minute delay
cronx.AddDelayTask(1*time.Minute, func(ctx context.Context) {
    sendWelcomeEmail(ctx)
}, 10*time.Second)

// Run once at a specific time
futureTime := time.Now().Add(30 * time.Minute)
cronx.AddOnceTask(futureTime, func(ctx context.Context) {
    generateNightlyReport(ctx)
})

Note: The older AddTask function remains available for backward compatibility but is marked as deprecated in cronx/cron.go because it lacks context support and panic recovery.

Starting and Managing the Scheduler

Initializing with Startup

The Startup function in cronx/cron.go begins processing registered jobs. It accepts a service identifier and an optional HTTP port for health checks.

// Start cron service without HTTP endpoint
if err := cronx.Startup("DATA_SYNC", ""); err != nil {
    log.Fatalf("cron startup failed: %v", err)
}

// Or with health endpoint on port 8081
if err := cronx.Startup("DATA_SYNC", "8081"); err != nil {
    log.Fatal(err)
}

Graceful Shutdown Handling

To prevent data loss or corruption, always call Shutdown when the application receives a termination signal. This method stops the scheduler, waits for running jobs to complete (respecting the provided context), and cleans up resources.

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := cronx.Shutdown("DATA_SYNC", ctx); err != nil {
    log.Printf("cron shutdown error: %v", err)
}

Production-Ready Cron Job Examples

Basic Recurring Task

This example demonstrates a minimal setup that prints a timestamp every 5 seconds.

package main

import (
    "context"
    "log"
    "time"

    "github.com/jom-io/gorig/cronx"
)

func main() {
    cronx.AddCronTask("@every 5s", func(ctx context.Context) {
        log.Println("tick at", time.Now())
    })

    if err := cronx.Startup("HEARTBEAT", ""); err != nil {
        log.Fatalf("failed to start cron: %v", err)
    }

    select {} // Block forever
}

Task with Timeout and Panic Recovery

The following job simulates unstable work that occasionally panics. The cronx wrapper in cronx/task.go automatically recovers and logs the error without crashing the scheduler.

cronx.AddCronTask("@every 10s", func(ctx context.Context) {
    // Simulate work that might panic
    if time.Now().Unix()%2 == 0 {
        panic("simulated failure")
    }
    log.Println("completed successfully")
}, 2*time.Second) // 2-second timeout

Scheduled One-Off Task

Use AddOnceTask to schedule maintenance windows or delayed notifications.

runAt := time.Now().Add(30 * time.Minute)
cronx.AddOnceTask(runAt, func(ctx context.Context) {
    log.Println("Generating nightly report at", time.Now())
    generateReport(ctx)
})

Graceful Shutdown Implementation

Integrate with Go’s signal handling for production deployments.

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := cronx.Shutdown("WORKER", ctx); err != nil {
    log.Printf("cron shutdown error: %v", err)
}

Key Source Files in the Cronx Module

Understanding the internal structure helps when debugging or extending functionality.

File Purpose Location
cronx/cron.go Public API surface including AddCronTask, Startup, and Shutdown github.com/jom-io/gorig/cronx/cron.go
cronx/task.go Internal Task struct definition, timeout enforcement, and panic recovery wrapper github.com/jom-io/gorig/cronx/task.go
cronx/logger.go Gorig-standard logger configuration for cron output github.com/jom-io/gorig/cronx/logger.go
test/cron_test.go Unit tests demonstrating valid usage patterns github.com/jom-io/gorig/test/cron_test.go

Summary

  • The cronx module in jom-io/gorig wraps robfig/cron to provide context-aware, panic-safe job scheduling.
  • Register tasks before startup using AddCronTask (cron expressions), AddEveryTask (intervals), AddDelayTask (delayed), or AddOnceTask (one-time).
  • Start the scheduler with Startup(serviceName, healthPort) and stop it gracefully with Shutdown(serviceName, ctx).
  • All tasks receive a context.Context and support optional timeouts and automatic panic recovery via the wrapper in cronx/task.go.

Frequently Asked Questions

What is the difference between AddCronTask and AddEveryTask?

AddCronTask accepts standard cron expressions like @every 1s or 0 0 * * *, while AddEveryTask takes a time.Duration directly (e.g., 10*time.Second) for simpler interval-based scheduling. Both functions support context propagation and timeout parameters, but AddEveryTask eliminates the need to parse cron syntax for simple recurring intervals.

How does cronx handle panics in scheduled jobs?

The cronx module automatically recovers from panics via the wrapper logic defined in cronx/task.go. When a task panics, the wrapper captures the error, logs it using the Gorig logger, and allows the scheduler to continue running subsequent jobs. This prevents a single faulty task from crashing the entire cron service.

Can I run a task only once at a specific time?

Yes, use the AddOnceTask function to schedule a one-time execution. Pass a time.Time value indicating when the task should run, along with your handler function. The task will execute exactly once at the specified time and then automatically remove itself from the scheduler.

What happens if I don't call Shutdown before the application exits?

If Shutdown is not called, the underlying robfig/cron scheduler may terminate abruptly, potentially interrupting running jobs and causing data loss or inconsistent state. Always call Shutdown with a context timeout during application termination (e.g., in signal handlers) to allow in-flight tasks to complete gracefully.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →