cronlib

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Feb 5, 2026 License: MIT Imports: 7 Imported by: 0

README

CronLib: High-Performance Go Cron Library

Go Reference

CronLib is a lightweight, thread-safe, and high-performance cron scheduling library for Go. It is designed to handle thousands of concurrent jobs with sub-millisecond precision, mirroring node-cron functionality but optimized for the Go ecosystem.

Key Features

  • 🚀 High Performance: Bitmask-based parser for O(1) matching of cron fields.
  • 🕒 Sub-millisecond Precision: Uses time.Timer for event-driven scheduling (no polling tickers).
  • 🔒 Thread-Safe: Safe for concurrent job management (adding, removing, stopping).
  • ⚙️ Overlap Policies: Control job execution overlap with Allow, Forbid, or Replace policies.
  • 💾 Persistence: Native SQLite support to track last run times and execution history.
  • 🌐 Distributed Locks: Redis integration for cluster-wide job synchronization.
  • 🖥️ Web Dashboard: Built-in UI to monitor job status and execution logs in real-time.
  • 🕒 Timezone Support: Full support for job-specific execution locations.

Versioning

This project follows Semantic Versioning 2.0.0.

We use GoReleaser to automate our release process. Every time a new version tag (e.g., v0.1.0) is pushed, a corresponding GitHub Release is created with an automatically generated changelog.

Requirements

  • Go: 1.24 or higher.
  • Targeting: Built for high-performance and modern Go concurrency patterns.

Installation

To install the latest version:

go get github.com/raythurman2386/cronlib

To install a specific version:

go get github.com/raythurman2386/cronlib@v0.1.2

Quick Start

package main

import (
	"fmt"
	"time"
	"github.com/raythurman2386/cronlib"
)

func main() {
	c := cronlib.NewCron()

	// Add a job: runs every 5 seconds
	c.AddJob("*/5 * * * * *", func() {
		fmt.Println("Tick:", time.Now().Format("15:04:05"))
	})

	c.Start()
	select {} // Keep running
}

Cron Syntax & Macros

CronLib supports standard 6-field cron syntax (sec, min, hour, dom, month, dow) and convenient macros:

  • @yearly, @annually: Run once a year.
  • @monthly: Run once a month.
  • @weekly: Run once a week.
  • @daily, @midnight: Run once a day.
  • @hourly: Run once an hour.
The @every Syntax

For simple fixed-interval schedules, use @every:

// Runs every 1 hour and 30 minutes
c.AddJob("@every 1h30m", func() {
    fmt.Println("Tick")
})

Concurrency Control (Overlap Policies)

CronLib provides fine-grained control over how jobs behave when a new execution is scheduled while a previous instance is still running.

Policy Description
OverlapAllow (Default) Allows multiple instances to run concurrently.
OverlapForbid Skips the execution if the previous instance is still running.
OverlapReplace Cancels the running instance and starts the new one immediately.
c.AddJobWithOptions("*/10 * * * * *", myTask, cronlib.JobOptions{
    Overlap: cronlib.OverlapReplace,
})

Middleware (Job Wrappers)

You can extend job behavior using composable wrappers. CronLib applies Recover, Lock (if configured), and Log (if configured) by default, but you can add custom logic.

Standard Wrappers:

  • Recover(): Catches panics and prevents the scheduler from crashing.
  • DelayIfStillRunning(): Queues execution if the previous run hasn't finished (ensures sequential execution).
  • SkipIfStillRunning(): Skips execution if busy (alternative to OverlapForbid).
c.AddJobWithOptions("@every 1s", myTask, cronlib.JobOptions{
    Wrappers: []cronlib.JobWrapper{
        cronlib.DelayIfStillRunning(),
        MyCustomMetricsWrapper(),
    },
})

Advanced Production Features

1. Persistent State (SQLite)

Track job history and recover schedules across restarts.

import "github.com/raythurman2386/cronlib/pkg/store/sqlite"

store, _ := sqlite.New("cron.db")
c.SetJobStore(store)
2. Distributed Locks (Redis)

Ensure a job runs only once across a cluster.

import "github.com/raythurman2386/cronlib/pkg/lock/redis"

lock := redis.New("localhost:6379")
c.SetDistLock(lock)
3. Web Dashboard

Embedded UI accessible at http://localhost:8080.

import "github.com/raythurman2386/cronlib/pkg/dashboard"

http.Handle("/", dashboard.NewHandler(c))
http.ListenAndServe(":8080", nil)

Monitoring

The Web Dashboard provides a live view of:

  • Job ID & Expression
  • Next Scheduled Run
  • Last Execution Time
  • Real-time Status (Running/Idle)

Examples

Explore more realistic implementation patterns in the examples/ directory:

  • 🚀 IoT Ingestion: High-frequency polling with sub-millisecond precision monitoring.
  • 🔒 Distributed Singleton: Cluster-wide job synchronization using Redis locks.
  • 💾 Persistent Recovery: Resuming schedules and tracking history using SQLite.
  • ⚙️ Overlap Control: Demonstrating Forbid and Replace policies for slow tasks.
  • 🌐 Full Stack: A complete implementation featuring the dashboard, persistence, and locking.

You can run any example using:

go run examples/iot/main.go

License

MIT

Documentation

Overview

Package cronlib provides a high-performance, thread-safe cron job scheduler for Go.

It allows scheduling jobs using standard cron syntax with seconds precision, as well as convenient macros like @every, @daily, etc.

Features

  • **Thread-Safe**: Safe for concurrent use from multiple goroutines.
  • **High Performance**: Uses a bitmask-based parser for fast matching.
  • **Seconds Precision**: Supports 6-field cron expressions (second, minute, hour, dom, month, dow).
  • **Job Overlap Policies**: Control behavior when a previous job run is still active (Allow, Forbid, Replace).
  • **Middleware/Wrappers**: Extensible job execution pipeline (logging, recovery, locking).
  • **Persistence**: Interfaces for storing job state and execution history.
  • **Distributed Locking**: Interfaces for cluster-wide job synchronization.

Syntax

The cron expression format is a space-separated string with 6 fields:

Field name   | Mandatory? | Allowed values  | Allowed special characters
----------   | ---------- | --------------  | --------------------------
Seconds      | Yes        | 0-59            | * / , -
Minutes      | Yes        | 0-59            | * / , -
Hours        | Yes        | 0-23            | * / , -
Day of month | Yes        | 1-31            | * / , -
Month        | Yes        | 1-12            | * / , -
Day of week  | Yes        | 0-6 (Sun-Sat)   | * / , -

Macros

Pre-defined macros can be used in place of the cron expression:

@yearly (or @annually)  Run once a year, midnight, Jan. 1st        (0 0 0 1 1 *)
@monthly                Run once a month, midnight, first of month  (0 0 0 1 * *)
@weekly                 Run once a week, midnight between Sat/Sun   (0 0 0 * * 0)
@daily (or @midnight)   Run once a day, midnight                    (0 0 0 * * *)
@hourly                 Run once an hour, beginning of hour         (0 0 * * * *)
@every <duration>       Run every <duration> (e.g. "@every 1h30m")

Usage Example

package main

import (
	"fmt"
	"time"
	"github.com/raythurman2386/cronlib"
)

func main() {
	c := cronlib.NewCron()

	// Run every 5 seconds
	c.AddJob("*\0575 * * * * *", func() {
		fmt.Println("Tick every 5s")
	})

	// Run every minute
	c.AddJob("@every 1m", func() {
		fmt.Println("Tick every 1m")
	})

	c.Start()
	defer c.Stop()

	// Block main thread
	select {}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cron

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

Cron is the main scheduler engine. It manages a list of jobs and executes them according to their schedule. Methods on Cron are thread-safe.

func NewCron

func NewCron() *Cron

NewCron creates a new Cron scheduler.

func (*Cron) AddJob

func (c *Cron) AddJob(spec string, cmd func()) (string, error)

AddJob adds a new job to the scheduler. Returns the job ID and error if spec is invalid.

Example
package main

import (
	"fmt"

	"github.com/raythurman2386/cronlib"
)

func main() {
	c := cronlib.NewCron()

	// Run every second
	// Note: We use a channel to ensure the example output is deterministic
	// for the purpose of this testable example.
	done := make(chan struct{})

	_, err := c.AddJob("* * * * * *", func() {
		fmt.Println("Job executed")
		close(done)
	})
	if err != nil {
		fmt.Println("Error scheduling job:", err)
		return
	}

	c.Start()
	defer c.Stop()

	<-done
}
Output:

Job executed

func (*Cron) AddJobWithOptions

func (c *Cron) AddJobWithOptions(spec string, cmd func(context.Context), opts JobOptions) (string, error)

AddJobWithOptions adds a new job with specific options.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/raythurman2386/cronlib"
)

func main() {
	c := cronlib.NewCron()

	// Define a job with specific options
	opts := cronlib.JobOptions{
		Overlap: cronlib.OverlapForbid, // Skip if previous run is still active
	}

	_, err := c.AddJobWithOptions("*/5 * * * * *", func(ctx context.Context) {
		fmt.Println("Job with context running")
	}, opts)
	if err != nil {
		fmt.Println("Error:", err)
	}

	c.Start()
	// Allow time for execution in a real app
	time.Sleep(1 * time.Second)
	c.Stop()
}

func (*Cron) GetJobs

func (c *Cron) GetJobs() []JobStatus

GetJobs returns the status of all scheduled jobs.

func (*Cron) RemoveJob

func (c *Cron) RemoveJob(id string)

RemoveJob removes a job by ID.

func (*Cron) SetDistLock

func (c *Cron) SetDistLock(l DistLock)

SetDistLock configures the distributed lock.

func (*Cron) SetJobStore

func (c *Cron) SetJobStore(s JobStore)

SetJobStore configures the persistence layer.

func (*Cron) Start

func (c *Cron) Start()

Start starts the scheduler loop.

func (*Cron) Stop

func (c *Cron) Stop()

Stop stops the scheduler.

type DistLock

type DistLock interface {
	// Lock attempts to acquire a lock for the key. Returns true if acquired.
	Lock(ctx context.Context, key string, ttl time.Duration) (bool, error)
	// Unlock releases the lock.
	Unlock(ctx context.Context, key string) error
}

DistLock defines distributed locking behavior.

type Expression

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

Expression represents the parsed cron expression using bitmasks.

func Parse

func Parse(spec string) (Expression, error)

Parse parses a 6-field cron string or a macro into an Expression.

The standard format is:

second minute hour day-of-month month day-of-week

Supported macros:

@yearly, @annually (0 0 0 1 1 *)
@monthly           (0 0 0 1 * *)
@weekly            (0 0 0 * * 0)
@daily, @midnight  (0 0 0 * * *)
@hourly            (0 0 * * * *)
@every <duration>  (e.g., "@every 1h30m")

func (Expression) Next

func (e Expression) Next(from time.Time) time.Time

Next returns the next execution time after `from`. Returns zero time if no match found within 5 years.

type Job

type Job struct {
	ID   string
	Spec string
	Expr Expression
	Cmd  func(context.Context) error
	// contains filtered or unexported fields
}

Job represents a scheduled task.

type JobCmd added in v0.1.2

type JobCmd = func(context.Context) error

JobCmd is the function signature for a job's command.

type JobOptions

type JobOptions struct {
	Overlap  OverlapPolicy
	Location *time.Location
	Wrappers []JobWrapper
}

JobOptions configuration for a job.

type JobStatus

type JobStatus struct {
	ID         string    `json:"id"`
	Expression string    `json:"expression"`
	NextRun    time.Time `json:"next_run"`
	LastRun    time.Time `json:"last_run"`
	Running    bool      `json:"running"`
}

JobStatus represents the state of a job.

type JobStore

type JobStore interface {
	GetLastRun(jobID string) (time.Time, error)
	SetLastRun(jobID string, t time.Time) error
	LogExecution(jobID string, start, end time.Time, success bool, out string) error
}

JobStore defines persistence for job state and history.

type JobWrapper

type JobWrapper = func(JobCmd) JobCmd

JobWrapper decorates a job execution function. It allows injecting custom logic before and after the job runs (middleware).

func Chain

func Chain(wrappers ...JobWrapper) JobWrapper

Chain combines multiple job wrappers into a single wrapper. Wrappers are executed in the order they are passed. Example: Chain(W1, W2) results in W1(W2(Job)).

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/raythurman2386/cronlib"
)

func main() {
	// Define a custom wrapper that logs execution
	loggingWrapper := func(next func(context.Context) error) func(context.Context) error {
		return func(ctx context.Context) error {
			fmt.Println("Starting job...")
			err := next(ctx)
			fmt.Println("Job finished")
			return err
		}
	}

	c := cronlib.NewCron()
	opts := cronlib.JobOptions{
		Wrappers: []cronlib.JobWrapper{loggingWrapper},
	}

	_, err := c.AddJobWithOptions("@every 1s", func(ctx context.Context) {
		fmt.Println("Doing work")
	}, opts)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	c.Start()
	// Wait for one run
	time.Sleep(1100 * time.Millisecond)
	c.Stop()
}

func DelayIfStillRunning

func DelayIfStillRunning() JobWrapper

DelayIfStillRunning ensures that job executions are sequential. If a job is triggered while a previous instance is running, it waits.

func Recover

func Recover() JobWrapper

Recover creates a wrapper that catches panics and returns them as errors.

func SkipIfStillRunning

func SkipIfStillRunning() JobWrapper

SkipIfStillRunning skips execution if the previous instance is still running. This is an alternative to the OverlapForbid policy, implemented as a wrapper.

type OverlapPolicy

type OverlapPolicy int

OverlapPolicy defines how to handle job overlaps.

const (
	// OverlapAllow allows multiple instances of the same job to run concurrently.
	OverlapAllow OverlapPolicy = iota
	// OverlapForbid skips execution if the previous instance is still running.
	OverlapForbid
	// OverlapReplace cancels the running instance and starts a new one.
	OverlapReplace
)

Directories

Path Synopsis
examples
distributed command
fullstack command
iot command
overlap command
persistence command
pkg
dashboard
Package dashboard provides a simple web UI for monitoring cron jobs.
Package dashboard provides a simple web UI for monitoring cron jobs.
lock/redis
Package redis provides a cronlib.DistLock implementation using Redis.
Package redis provides a cronlib.DistLock implementation using Redis.
store/sqlite
Package sqlite provides a cronlib.JobStore implementation using SQLite.
Package sqlite provides a cronlib.JobStore implementation using SQLite.

Jump to

Keyboard shortcuts

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