gocron

package module
v2.12.4 Latest Latest
Warning

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

Go to latest
Published: Nov 20, 2024 License: MIT Imports: 17 Imported by: 222

README

gocron: A Golang Job Scheduling Package

CI State Go Report Card Go Doc

gocron is a job scheduling package which lets you run Go functions at pre-determined intervals.

If you want to chat, you can find us on Slack at

Quick Start

go get github.com/go-co-op/gocron/v2
package main

import (
	"fmt"
	"time"

	"github.com/go-co-op/gocron/v2"
)

func main() {
	// create a scheduler
	s, err := gocron.NewScheduler()
	if err != nil {
		// handle error
	}

	// add a job to the scheduler
	j, err := s.NewJob(
		gocron.DurationJob(
			10*time.Second,
		),
		gocron.NewTask(
			func(a string, b int) {
				// do things
			},
			"hello",
			1,
		),
	)
	if err != nil {
		// handle error
	}
	// each job has a unique id
	fmt.Println(j.ID())

	// start the scheduler
	s.Start()

	// block until you are ready to shut down
	select {
	case <-time.After(time.Minute):
	}

	// when you're done, shut it down
	err = s.Shutdown()
	if err != nil {
		// handle error
	}
}

Examples

Concepts

  • Job: The job encapsulates a "task", which is made up of a go function and any function parameters. The Job then provides the scheduler with the time the job should next be scheduled to run.
  • Scheduler: The scheduler keeps track of all the jobs and sends each job to the executor when it is ready to be run.
  • Executor: The executor calls the job's task and manages the complexities of different job execution timing requirements (e.g. singletons that shouldn't overrun each other, limiting the max number of jobs running)

Features

Job types

Jobs can be run at various intervals.

  • Duration: Jobs can be run at a fixed time.Duration.
  • Random duration: Jobs can be run at a random time.Duration between a min and max.
  • Cron: Jobs can be run using a crontab.
  • Daily: Jobs can be run every x days at specific times.
  • Weekly: Jobs can be run every x weeks on specific days of the week and at specific times.
  • Monthly: Jobs can be run every x months on specific days of the month and at specific times.
  • One time: Jobs can be run at specific time(s) (either once or many times).
Concurrency Limits

Jobs can be limited individually or across the entire scheduler.

  • Per job limiting with singleton mode: Jobs can be limited to a single concurrent execution that either reschedules (skips overlapping executions) or queues (waits for the previous execution to finish).
  • Per scheduler limiting with limit mode: Jobs can be limited to a certain number of concurrent executions across the entire scheduler using either reschedule (skip when the limit is met) or queue (jobs are added to a queue to wait for the limit to be available).
  • Note: A scheduler limit and a job limit can both be enabled.
Distributed instances of gocron

Multiple instances of gocron can be run.

  • Elector: An elector can be used to elect a single instance of gocron to run as the primary with the other instances checking to see if a new leader needs to be elected.
    • Implementations: go-co-op electors (don't see what you need? request on slack to get a repo created to contribute it!)
  • Locker: A locker can be used to lock each run of a job to a single instance of gocron. Locker can be at job or scheduler, if it is defined both at job and scheduler then locker of job will take precedence.
    • Implementations: go-co-op lockers (don't see what you need? request on slack to get a repo created to contribute it!)
Events

Job events can trigger actions.

Options

Many job and scheduler options are available.

  • Job options: Job options can be set when creating a job using NewJob.
  • Global job options: Global job options can be set when creating a scheduler using NewScheduler and the WithGlobalJobOptions option.
  • Scheduler options: Scheduler options can be set when creating a scheduler using NewScheduler.
Logging

Logs can be enabled.

  • Logger: The Logger interface can be implemented with your desired logging library. The provided NewLogger uses the standard library's log package.
Metrics

Metrics may be collected from the execution of each job.

  • Monitor: A monitor can be used to collect metrics for each job from a scheduler.
    • Implementations: go-co-op monitors (don't see what you need? request on slack to get a repo created to contribute it!)
Testing

The gocron library is set up to enable testing.

Supporters

We appreciate the support for free and open source software!

This project is supported by:

Star History

Star History Chart

Documentation

Index

Examples

Constants

View Source
const (
	// LimitModeReschedule causes jobs reaching the limit set in
	// WithLimitConcurrentJobs or WithSingletonMode to be skipped
	// and rescheduled for the next run time rather than being
	// queued up to wait.
	LimitModeReschedule = 1

	// LimitModeWait causes jobs reaching the limit set in
	// WithLimitConcurrentJobs or WithSingletonMode to wait
	// in a queue until a slot becomes available to run.
	//
	// Note: this mode can produce unpredictable results as
	// job execution order isn't guaranteed. For example, a job that
	// executes frequently may pile up in the wait queue and be executed
	// many times back to back when the queue opens.
	//
	// Warning: do not use this mode if your jobs will continue to stack
	// up beyond the ability of the limit workers to keep up. An example of
	// what NOT to do:
	//
	//     s, _ := gocron.NewScheduler(gocron.WithLimitConcurrentJobs)
	//     s.NewJob(
	//         gocron.DurationJob(
	//				time.Second,
	//				Task{
	//					Function: func() {
	//						time.Sleep(10 * time.Second)
	//					},
	//				},
	//			),
	//      )
	LimitModeWait = 2
)

Variables

View Source
var (
	ErrCronJobInvalid                = fmt.Errorf("gocron: CronJob: invalid crontab")
	ErrCronJobParse                  = fmt.Errorf("gocron: CronJob: crontab parse failure")
	ErrDailyJobAtTimeNil             = fmt.Errorf("gocron: DailyJob: atTime within atTimes must not be nil")
	ErrDailyJobAtTimesNil            = fmt.Errorf("gocron: DailyJob: atTimes must not be nil")
	ErrDailyJobHours                 = fmt.Errorf("gocron: DailyJob: atTimes hours must be between 0 and 23 inclusive")
	ErrDailyJobZeroInterval          = fmt.Errorf("gocron: DailyJob: interval must be greater than 0")
	ErrDailyJobMinutesSeconds        = fmt.Errorf("gocron: DailyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
	ErrDurationJobIntervalZero       = fmt.Errorf("gocron: DurationJob: time interval is 0")
	ErrDurationRandomJobMinMax       = fmt.Errorf("gocron: DurationRandomJob: minimum duration must be less than maximum duration")
	ErrEventListenerFuncNil          = fmt.Errorf("gocron: eventListenerFunc must not be nil")
	ErrJobNotFound                   = fmt.Errorf("gocron: job not found")
	ErrJobRunNowFailed               = fmt.Errorf("gocron: Job: RunNow: scheduler unreachable")
	ErrMonthlyJobDays                = fmt.Errorf("gocron: MonthlyJob: daysOfTheMonth must be between 31 and -31 inclusive, and not 0")
	ErrMonthlyJobAtTimeNil           = fmt.Errorf("gocron: MonthlyJob: atTime within atTimes must not be nil")
	ErrMonthlyJobAtTimesNil          = fmt.Errorf("gocron: MonthlyJob: atTimes must not be nil")
	ErrMonthlyJobDaysNil             = fmt.Errorf("gocron: MonthlyJob: daysOfTheMonth must not be nil")
	ErrMonthlyJobHours               = fmt.Errorf("gocron: MonthlyJob: atTimes hours must be between 0 and 23 inclusive")
	ErrMonthlyJobZeroInterval        = fmt.Errorf("gocron: MonthlyJob: interval must be greater than 0")
	ErrMonthlyJobMinutesSeconds      = fmt.Errorf("gocron: MonthlyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
	ErrNewJobTaskNil                 = fmt.Errorf("gocron: NewJob: Task must not be nil")
	ErrNewJobTaskNotFunc             = fmt.Errorf("gocron: NewJob: Task.Function must be of kind reflect.Func")
	ErrNewJobWrongNumberOfParameters = fmt.Errorf("gocron: NewJob: Number of provided parameters does not match expected")
	ErrNewJobWrongTypeOfParameters   = fmt.Errorf("gocron: NewJob: Type of provided parameters does not match expected")
	ErrOneTimeJobStartDateTimePast   = fmt.Errorf("gocron: OneTimeJob: start must not be in the past")
	ErrStopExecutorTimedOut          = fmt.Errorf("gocron: timed out waiting for executor to stop")
	ErrStopJobsTimedOut              = fmt.Errorf("gocron: timed out waiting for jobs to finish")
	ErrStopSchedulerTimedOut         = fmt.Errorf("gocron: timed out waiting for scheduler to stop")
	ErrWeeklyJobAtTimeNil            = fmt.Errorf("gocron: WeeklyJob: atTime within atTimes must not be nil")
	ErrWeeklyJobAtTimesNil           = fmt.Errorf("gocron: WeeklyJob: atTimes must not be nil")
	ErrWeeklyJobDaysOfTheWeekNil     = fmt.Errorf("gocron: WeeklyJob: daysOfTheWeek must not be nil")
	ErrWeeklyJobHours                = fmt.Errorf("gocron: WeeklyJob: atTimes hours must be between 0 and 23 inclusive")
	ErrWeeklyJobZeroInterval         = fmt.Errorf("gocron: WeeklyJob: interval must be greater than 0")
	ErrWeeklyJobMinutesSeconds       = fmt.Errorf("gocron: WeeklyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
	ErrPanicRecovered                = fmt.Errorf("gocron: panic recovered")
	ErrWithClockNil                  = fmt.Errorf("gocron: WithClock: clock must not be nil")
	ErrWithDistributedElectorNil     = fmt.Errorf("gocron: WithDistributedElector: elector must not be nil")
	ErrWithDistributedLockerNil      = fmt.Errorf("gocron: WithDistributedLocker: locker must not be nil")
	ErrWithDistributedJobLockerNil   = fmt.Errorf("gocron: WithDistributedJobLocker: locker must not be nil")
	ErrWithIdentifierNil             = fmt.Errorf("gocron: WithIdentifier: identifier must not be nil")
	ErrWithLimitConcurrentJobsZero   = fmt.Errorf("gocron: WithLimitConcurrentJobs: limit must be greater than 0")
	ErrWithLocationNil               = fmt.Errorf("gocron: WithLocation: location must not be nil")
	ErrWithLoggerNil                 = fmt.Errorf("gocron: WithLogger: logger must not be nil")
	ErrWithMonitorNil                = fmt.Errorf("gocron: WithMonitor: monitor must not be nil")
	ErrWithNameEmpty                 = fmt.Errorf("gocron: WithName: name must not be empty")
	ErrWithStartDateTimePast         = fmt.Errorf("gocron: WithStartDateTime: start must not be in the past")
	ErrWithStopDateTimePast          = fmt.Errorf("gocron: WithStopDateTime: end must not be in the past")
	ErrStartTimeLaterThanEndTime     = fmt.Errorf("gocron: WithStartDateTime: start must not be later than end")
	ErrStopTimeEarlierThanStartTime  = fmt.Errorf("gocron: WithStopDateTime: end must not be earlier than start")
	ErrWithStopTimeoutZeroOrNegative = fmt.Errorf("gocron: WithStopTimeout: timeout must be greater than 0")
)

Public error definitions

Functions

This section is empty.

Types

type AtTime

type AtTime func() atTime

AtTime defines a function that returns the internal atTime

func NewAtTime

func NewAtTime(hours, minutes, seconds uint) AtTime

NewAtTime provide the hours, minutes and seconds at which the job should be run

type AtTimes

type AtTimes func() []AtTime

AtTimes define a list of AtTime

func NewAtTimes

func NewAtTimes(atTime AtTime, atTimes ...AtTime) AtTimes

NewAtTimes provide the hours, minutes and seconds at which the job should be run

type DaysOfTheMonth

type DaysOfTheMonth func() days

DaysOfTheMonth defines a function that returns a list of days.

func NewDaysOfTheMonth

func NewDaysOfTheMonth(day int, moreDays ...int) DaysOfTheMonth

NewDaysOfTheMonth provide the days of the month the job should run. The days can be positive 1 to 31 and/or negative -31 to -1. Negative values count backwards from the end of the month. For example: -1 == the last day of the month.

-5 == 5 days before the end of the month.

type Elector

type Elector interface {
	// IsLeader should return  nil if the job should be scheduled by the instance
	// making the request and an error if the job should not be scheduled.
	IsLeader(context.Context) error
}

Elector determines the leader from instances asking to be the leader. Only the leader runs jobs. If the leader goes down, a new leader will be elected.

type EventListener

type EventListener func(*internalJob) error

EventListener defines the constructor for event listeners that can be used to listen for job events.

func AfterJobRuns

func AfterJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener

AfterJobRuns is used to listen for when a job has run without an error, and then run the provided function.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithEventListeners(
		AfterJobRuns(
			func(jobID uuid.UUID, jobName string) {
				// do something after the job completes
			},
		),
	),
)
Output:

func AfterJobRunsWithError

func AfterJobRunsWithError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListener

AfterJobRunsWithError is used to listen for when a job has run and returned an error, and then run the provided function.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithEventListeners(
		AfterJobRunsWithError(
			func(jobID uuid.UUID, jobName string, err error) {
				// do something when the job returns an error
			},
		),
	),
)
Output:

func AfterJobRunsWithPanic added in v2.6.0

func AfterJobRunsWithPanic(eventListenerFunc func(jobID uuid.UUID, jobName string, recoverData any)) EventListener

AfterJobRunsWithPanic is used to listen for when a job has run and returned panicked recover data, and then run the provided function.

func AfterLockError added in v2.6.0

func AfterLockError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListener

AfterLockError is used to when the distributed locker returns an error and then run the provided function.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithDistributedJobLocker(&errorLocker{}),
	WithEventListeners(
		AfterLockError(
			func(jobID uuid.UUID, jobName string, err error) {
				// do something immediately before the job is run
			},
		),
	),
)
Output:

func BeforeJobRuns

func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener

BeforeJobRuns is used to listen for when a job is about to run and then run the provided function.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithEventListeners(
		BeforeJobRuns(
			func(jobID uuid.UUID, jobName string) {
				// do something immediately before the job is run
			},
		),
	),
)
Output:

type Job

type Job interface {
	// ID returns the job's unique identifier.
	ID() uuid.UUID
	// LastRun returns the time of the job's last run
	LastRun() (time.Time, error)
	// Name returns the name defined on the job.
	Name() string
	// NextRun returns the time of the job's next scheduled run.
	NextRun() (time.Time, error)
	// NextRuns returns the requested number of calculated next run values.
	NextRuns(int) ([]time.Time, error)
	// RunNow runs the job once, now. This does not alter
	// the existing run schedule, and will respect all job
	// and scheduler limits. This means that running a job now may
	// cause the job's regular interval to be rescheduled due to
	// the instance being run by RunNow blocking your run limit.
	RunNow() error
	// Tags returns the job's string tags.
	Tags() []string
}

Job provides the available methods on the job available to the caller.

Example (Id)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
)

fmt.Println(j.ID())
Output:

Example (LastRun)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
)

fmt.Println(j.LastRun())
Output:

Example (Name)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithName("foobar"),
)

fmt.Println(j.Name())
Output:

foobar
Example (NextRun)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
)

nextRun, _ := j.NextRun()
fmt.Println(nextRun)
Output:

Example (NextRuns)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
)

nextRuns, _ := j.NextRuns(5)
fmt.Println(nextRuns)
Output:

Example (RunNow)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	MonthlyJob(
		1,
		NewDaysOfTheMonth(3, -5, -1),
		NewAtTimes(
			NewAtTime(10, 30, 0),
			NewAtTime(11, 15, 0),
		),
	),
	NewTask(
		func() {},
	),
)
s.Start()
// Runs the job one time now, without impacting the schedule
_ = j.RunNow()
Output:

Example (Tags)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithTags("foo", "bar"),
)

fmt.Println(j.Tags())
Output:

[foo bar]

type JobDefinition

type JobDefinition interface {
	// contains filtered or unexported methods
}

JobDefinition defines the interface that must be implemented to create a job from the definition.

func CronJob

func CronJob(crontab string, withSeconds bool) JobDefinition

CronJob defines a new job using the crontab syntax: `* * * * *`. An optional 6th field can be used at the beginning if withSeconds is set to true: `* * * * * *`. The timezone can be set on the Scheduler using WithLocation, or in the crontab in the form `TZ=America/Chicago * * * * *` or `CRON_TZ=America/Chicago * * * * *`

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	CronJob(
		// standard cron tab parsing
		"1 * * * *",
		false,
	),
	NewTask(
		func() {},
	),
)
_, _ = s.NewJob(
	CronJob(
		// optionally include seconds as the first field
		"* 1 * * * *",
		true,
	),
	NewTask(
		func() {},
	),
)
Output:

func DailyJob

func DailyJob(interval uint, atTimes AtTimes) JobDefinition

DailyJob runs the job on the interval of days, and at the set times. By default, the job will start the next available day, considering the last run to be now, and the time and day based on the interval and times you input. This means, if you select an interval greater than 1, your job by default will run X (interval) days from now if there are no atTimes left in the current day. You can use WithStartAt to tell the scheduler to start the job sooner.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DailyJob(
		1,
		NewAtTimes(
			NewAtTime(10, 30, 0),
			NewAtTime(14, 0, 0),
		),
	),
	NewTask(
		func(a, b string) {},
		"a",
		"b",
	),
)
Output:

func DurationJob

func DurationJob(duration time.Duration) JobDefinition

DurationJob defines a new job using time.Duration for the interval.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second*5,
	),
	NewTask(
		func() {},
	),
)
Output:

func DurationRandomJob

func DurationRandomJob(minDuration, maxDuration time.Duration) JobDefinition

DurationRandomJob defines a new job that runs on a random interval between the min and max duration values provided.

To achieve a similar behavior as tools that use a splay/jitter technique consider the median value as the baseline and the difference between the max-median or median-min as the splay/jitter.

For example, if you want a job to run every 5 minutes, but want to add up to 1 min of jitter to the interval, you could use DurationRandomJob(4*time.Minute, 6*time.Minute)

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationRandomJob(
		time.Second,
		5*time.Second,
	),
	NewTask(
		func() {},
	),
)
Output:

func MonthlyJob

func MonthlyJob(interval uint, daysOfTheMonth DaysOfTheMonth, atTimes AtTimes) JobDefinition

MonthlyJob runs the job on the interval of months, on the specific days of the month specified, and at the set times. Days of the month can be 1 to 31 or negative (-1 to -31), which count backwards from the end of the month. E.g. -1 is the last day of the month.

If a day of the month is selected that does not exist in all months (e.g. 31st) any month that does not have that day will be skipped.

By default, the job will start the next available day, considering the last run to be now, and the time and month based on the interval, days and times you input. This means, if you select an interval greater than 1, your job by default will run X (interval) months from now if there are no daysOfTheMonth left in the current month. You can use WithStartAt to tell the scheduler to start the job sooner.

Carefully consider your configuration!

  • For example: an interval of 2 months on the 31st of each month, starting 12/31 would skip Feb, April, June, and next run would be in August.
Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	MonthlyJob(
		1,
		NewDaysOfTheMonth(3, -5, -1),
		NewAtTimes(
			NewAtTime(10, 30, 0),
			NewAtTime(11, 15, 0),
		),
	),
	NewTask(
		func() {},
	),
)
Output:

func OneTimeJob added in v2.1.0

func OneTimeJob(startAt OneTimeJobStartAtOption) JobDefinition

OneTimeJob is to run a job once at a specified time and not on any regular schedule.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

// run a job once, immediately
_, _ = s.NewJob(
	OneTimeJob(
		OneTimeJobStartImmediately(),
	),
	NewTask(
		func() {},
	),
)
// run a job once in 10 seconds
_, _ = s.NewJob(
	OneTimeJob(
		OneTimeJobStartDateTime(time.Now().Add(10*time.Second)),
	),
	NewTask(
		func() {},
	),
)
// run job twice - once in 10 seconds and once in 55 minutes
n := time.Now()
_, _ = s.NewJob(
	OneTimeJob(
		OneTimeJobStartDateTimes(
			n.Add(10*time.Second),
			n.Add(55*time.Minute),
		),
	),
	NewTask(func() {}),
)

s.Start()
Output:

func WeeklyJob

func WeeklyJob(interval uint, daysOfTheWeek Weekdays, atTimes AtTimes) JobDefinition

WeeklyJob runs the job on the interval of weeks, on the specific days of the week specified, and at the set times.

By default, the job will start the next available day, considering the last run to be now, and the time and day based on the interval, days and times you input. This means, if you select an interval greater than 1, your job by default will run X (interval) weeks from now if there are no daysOfTheWeek left in the current week. You can use WithStartAt to tell the scheduler to start the job sooner.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	WeeklyJob(
		2,
		NewWeekdays(time.Tuesday, time.Wednesday, time.Saturday),
		NewAtTimes(
			NewAtTime(1, 30, 0),
			NewAtTime(12, 0, 30),
		),
	),
	NewTask(
		func() {},
	),
)
Output:

type JobOption

type JobOption func(*internalJob, time.Time) error

JobOption defines the constructor for job options.

func WithDistributedJobLocker added in v2.2.10

func WithDistributedJobLocker(locker Locker) JobOption

WithDistributedJobLocker sets the locker to be used by multiple Scheduler instances to ensure that only one instance of each job is run.

func WithEventListeners

func WithEventListeners(eventListeners ...EventListener) JobOption

WithEventListeners sets the event listeners that should be run for the job.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithEventListeners(
		AfterJobRuns(
			func(jobID uuid.UUID, jobName string) {
				// do something after the job completes
			},
		),
		AfterJobRunsWithError(
			func(jobID uuid.UUID, jobName string, err error) {
				// do something when the job returns an error
			},
		),
		BeforeJobRuns(
			func(jobID uuid.UUID, jobName string) {
				// do something immediately before the job is run
			},
		),
	),
)
Output:

func WithIdentifier added in v2.9.0

func WithIdentifier(id uuid.UUID) JobOption

WithIdentifier sets the identifier for the job. The identifier is used to uniquely identify the job and is used for logging and metrics.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
	WithIdentifier(uuid.MustParse("87b95dfc-3e71-11ef-9454-0242ac120002")),
)
fmt.Println(j.ID())
Output:

87b95dfc-3e71-11ef-9454-0242ac120002

func WithLimitedRuns

func WithLimitedRuns(limit uint) JobOption

WithLimitedRuns limits the number of executions of this job to n. Upon reaching the limit, the job is removed from the scheduler.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Millisecond,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d\n", one, two)
		},
		"one", 2,
	),
	WithLimitedRuns(1),
)
s.Start()

time.Sleep(100 * time.Millisecond)
_ = s.StopJobs()
fmt.Printf("no jobs in scheduler: %v\n", s.Jobs())
Output:

one, 2
no jobs in scheduler: []

func WithName

func WithName(name string) JobOption

WithName sets the name of the job. Name provides a human-readable identifier for the job.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
	WithName("job 1"),
)
fmt.Println(j.Name())
Output:

job 1

func WithSingletonMode

func WithSingletonMode(mode LimitMode) JobOption

WithSingletonMode keeps the job from running again if it is already running. This is useful for jobs that should not overlap, and that occasionally (but not consistently) run longer than the interval between job runs.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {
			// this job will skip half it's executions
			// and effectively run every 2 seconds
			time.Sleep(1500 * time.Second)
		},
	),
	WithSingletonMode(LimitModeReschedule),
)
Output:

func WithStartAt

func WithStartAt(option StartAtOption) JobOption

WithStartAt sets the option for starting the job at a specific datetime.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

start := time.Date(9999, 9, 9, 9, 9, 9, 9, time.UTC)

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
	WithStartAt(
		WithStartDateTime(start),
	),
)
s.Start()

next, _ := j.NextRun()
fmt.Println(next)

_ = s.StopJobs()
Output:

9999-09-09 09:09:09.000000009 +0000 UTC

func WithStopAt added in v2.11.0

func WithStopAt(option StopAtOption) JobOption

WithStopAt sets the option for stopping the job from running after the specified time.

func WithTags

func WithTags(tags ...string) JobOption

WithTags sets the tags for the job. Tags provide a way to identify jobs by a set of tags and remove multiple jobs by tag.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
	WithTags("tag1", "tag2", "tag3"),
)
fmt.Println(j.Tags())
Output:

[tag1 tag2 tag3]

type JobStatus added in v2.2.0

type JobStatus string

JobStatus is the status of job run that should be collected with the metric.

const (
	Fail                 JobStatus = "fail"
	Success              JobStatus = "success"
	Skip                 JobStatus = "skip"
	SingletonRescheduled JobStatus = "singleton_rescheduled"
)

The different statuses of job that can be used.

type LimitMode

type LimitMode int

LimitMode defines the modes used for handling jobs that reach the limit provided in WithLimitConcurrentJobs

type Lock

type Lock interface {
	Unlock(ctx context.Context) error
}

Lock represents an obtained lock. The lock is released after the execution of the job by the scheduler.

type Locker

type Locker interface {
	// Lock if an error is returned by lock, the job will not be scheduled.
	Lock(ctx context.Context, key string) (Lock, error)
}

Locker represents the required interface to lock jobs when running multiple schedulers. The lock is held for the duration of the job's run, and it is expected that the locker implementation handles time splay between schedulers. The lock key passed is the job's name - which, if not set, defaults to the go function's name, e.g. "pkg.myJob" for func myJob() {} in pkg

type LogLevel

type LogLevel int

LogLevel is the level of logging that should be logged when using the basic NewLogger.

const (
	LogLevelError LogLevel = iota
	LogLevelWarn
	LogLevelInfo
	LogLevelDebug
)

The different log levels that can be used.

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Error(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
}

Logger is the interface that wraps the basic logging methods used by gocron. The methods are modeled after the standard library slog package. The default logger is a no-op logger. To enable logging, use one of the provided New*Logger functions or implement your own Logger. The actual level of Log that is logged is handled by the implementation.

func NewLogger

func NewLogger(level LogLevel) Logger

NewLogger returns a new Logger that logs at the given level.

type Monitor added in v2.2.0

type Monitor interface {
	// IncrementJob will provide details about the job and expects the underlying implementation
	// to handle instantiating and incrementing a value
	IncrementJob(id uuid.UUID, name string, tags []string, status JobStatus)
	// RecordJobTiming will provide details about the job and the timing and expects the underlying implementation
	// to handle instantiating and recording the value
	RecordJobTiming(startTime, endTime time.Time, id uuid.UUID, name string, tags []string)
}

Monitor represents the interface to collect jobs metrics.

type OneTimeJobStartAtOption added in v2.1.0

type OneTimeJobStartAtOption func(*internalJob) []time.Time

OneTimeJobStartAtOption defines when the one time job is run

func OneTimeJobStartDateTime added in v2.1.0

func OneTimeJobStartDateTime(start time.Time) OneTimeJobStartAtOption

OneTimeJobStartDateTime sets the date & time at which the job should run. This datetime must be in the future (according to the scheduler clock).

func OneTimeJobStartDateTimes added in v2.7.0

func OneTimeJobStartDateTimes(times ...time.Time) OneTimeJobStartAtOption

OneTimeJobStartDateTimes sets the date & times at which the job should run. At least one of the date/times must be in the future (according to the scheduler clock).

func OneTimeJobStartImmediately added in v2.1.0

func OneTimeJobStartImmediately() OneTimeJobStartAtOption

OneTimeJobStartImmediately tells the scheduler to run the one time job immediately.

type Scheduler

type Scheduler interface {
	// Jobs returns all the jobs currently in the scheduler.
	Jobs() []Job
	// NewJob creates a new job in the Scheduler. The job is scheduled per the provided
	// definition when the Scheduler is started. If the Scheduler is already running
	// the job will be scheduled when the Scheduler is started.
	NewJob(JobDefinition, Task, ...JobOption) (Job, error)
	// RemoveByTags removes all jobs that have at least one of the provided tags.
	RemoveByTags(...string)
	// RemoveJob removes the job with the provided id.
	RemoveJob(uuid.UUID) error
	// Shutdown should be called when you no longer need
	// the Scheduler or Job's as the Scheduler cannot
	// be restarted after calling Shutdown. This is similar
	// to a Close or Cleanup method and is often deferred after
	// starting the scheduler.
	Shutdown() error
	// Start begins scheduling jobs for execution based
	// on each job's definition. Job's added to an already
	// running scheduler will be scheduled immediately based
	// on definition. Start is non-blocking.
	Start()
	// StopJobs stops the execution of all jobs in the scheduler.
	// This can be useful in situations where jobs need to be
	// paused globally and then restarted with Start().
	StopJobs() error
	// Update replaces the existing Job's JobDefinition with the provided
	// JobDefinition. The Job's Job.ID() remains the same.
	Update(uuid.UUID, JobDefinition, Task, ...JobOption) (Job, error)
	// JobsWaitingInQueue number of jobs waiting in Queue in case of LimitModeWait
	// In case of LimitModeReschedule or no limit it will be always zero
	JobsWaitingInQueue() int
}

Scheduler defines the interface for the Scheduler.

Example (Jobs)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		10*time.Second,
	),
	NewTask(
		func() {},
	),
)
fmt.Println(len(s.Jobs()))
Output:

1
Example (NewJob)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, err := s.NewJob(
	DurationJob(
		10*time.Second,
	),
	NewTask(
		func() {},
	),
)
if err != nil {
	panic(err)
}
fmt.Println(j.ID())
Output:

Example (RemoveByTags)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithTags("tag1"),
)
_, _ = s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
	WithTags("tag2"),
)
fmt.Println(len(s.Jobs()))

s.RemoveByTags("tag1", "tag2")

fmt.Println(len(s.Jobs()))
Output:

2
0
Example (RemoveJob)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func() {},
	),
)

fmt.Println(len(s.Jobs()))

_ = s.RemoveJob(j.ID())

fmt.Println(len(s.Jobs()))
Output:

1
0
Example (Shutdown)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()
Output:

Example (Start)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	CronJob(
		"* * * * *",
		false,
	),
	NewTask(
		func() {},
	),
)

s.Start()
Output:

Example (StopJobs)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

_, _ = s.NewJob(
	CronJob(
		"* * * * *",
		false,
	),
	NewTask(
		func() {},
	),
)

s.Start()

_ = s.StopJobs()
Output:

Example (Update)
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

j, _ := s.NewJob(
	CronJob(
		"* * * * *",
		false,
	),
	NewTask(
		func() {},
	),
)

s.Start()

// after some time, need to change the job

j, _ = s.Update(
	j.ID(),
	DurationJob(
		5*time.Second,
	),
	NewTask(
		func() {},
	),
)
Output:

func NewScheduler

func NewScheduler(options ...SchedulerOption) (Scheduler, error)

NewScheduler creates a new Scheduler instance. The Scheduler is not started until Start() is called.

NewJob will add jobs to the Scheduler, but they will not be scheduled until Start() is called.

Example
s, _ := NewScheduler()
defer func() { _ = s.Shutdown() }()

fmt.Println(s.Jobs())
Output:

type SchedulerOption

type SchedulerOption func(*scheduler) error

SchedulerOption defines the function for setting options on the Scheduler.

func WithClock

func WithClock(clock clockwork.Clock) SchedulerOption

WithClock sets the clock used by the Scheduler to the clock provided. See https://github.com/jonboulle/clockwork

Example
fakeClock := clockwork.NewFakeClock()
s, _ := NewScheduler(
	WithClock(fakeClock),
)
var wg sync.WaitGroup
wg.Add(1)
_, _ = s.NewJob(
	DurationJob(
		time.Second*5,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d\n", one, two)
			wg.Done()
		},
		"one", 2,
	),
)
s.Start()
fakeClock.BlockUntil(1)
fakeClock.Advance(time.Second * 5)
wg.Wait()
_ = s.StopJobs()
Output:

one, 2

func WithDistributedElector

func WithDistributedElector(elector Elector) SchedulerOption

WithDistributedElector sets the elector to be used by multiple Scheduler instances to determine who should be the leader. Only the leader runs jobs, while non-leaders wait and continue to check if a new leader has been elected.

Example
//var _ gocron.Elector = (*myElector)(nil)
//
//type myElector struct{}
//
//func (m myElector) IsLeader(_ context.Context) error {
//	return nil
//}
//
//elector := &myElector{}
//
//_, _ = gocron.NewScheduler(
//	gocron.WithDistributedElector(elector),
//)
Output:

func WithDistributedLocker

func WithDistributedLocker(locker Locker) SchedulerOption

WithDistributedLocker sets the locker to be used by multiple Scheduler instances to ensure that only one instance of each job is run.

Example
//var _ gocron.Locker = (*myLocker)(nil)
//
//type myLocker struct{}
//
//func (m myLocker) Lock(ctx context.Context, key string) (Lock, error) {
//	return &testLock, nil
//}
//
//var _ Lock = (*testLock)(nil)
//
//type testLock struct {
//}
//
//func (t testLock) Unlock(_ context.Context) error {
//	return nil
//}
//
//locker := &myLocker{}
//
//_, _ = gocron.NewScheduler(
//	gocron.WithDistributedLocker(locker),
//)
Output:

func WithGlobalJobOptions

func WithGlobalJobOptions(jobOptions ...JobOption) SchedulerOption

WithGlobalJobOptions sets JobOption's that will be applied to all jobs added to the scheduler. JobOption's set on the job itself will override if the same JobOption is set globally.

Example
s, _ := NewScheduler(
	WithGlobalJobOptions(
		WithTags("tag1", "tag2", "tag3"),
	),
)

j, _ := s.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
)
// The job will have the globally applied tags
fmt.Println(j.Tags())

s2, _ := NewScheduler(
	WithGlobalJobOptions(
		WithTags("tag1", "tag2", "tag3"),
	),
)
j2, _ := s2.NewJob(
	DurationJob(
		time.Second,
	),
	NewTask(
		func(one string, two int) {
			fmt.Printf("%s, %d", one, two)
		},
		"one", 2,
	),
	WithTags("tag4", "tag5", "tag6"),
)
// The job will have the tags set specifically on the job
// overriding those set globally by the scheduler
fmt.Println(j2.Tags())
Output:

[tag1 tag2 tag3]
[tag4 tag5 tag6]

func WithLimitConcurrentJobs

func WithLimitConcurrentJobs(limit uint, mode LimitMode) SchedulerOption

WithLimitConcurrentJobs sets the limit and mode to be used by the Scheduler for limiting the number of jobs that may be running at a given time.

Note: the limit mode selected for WithLimitConcurrentJobs takes initial precedence in the event you are also running a limit mode at the job level using WithSingletonMode.

Warning: a single time consuming job can dominate your limit in the event you are running both the scheduler limit WithLimitConcurrentJobs(1, LimitModeWait) and a job limit WithSingletonMode(LimitModeReschedule).

Example
_, _ = NewScheduler(
	WithLimitConcurrentJobs(
		1,
		LimitModeReschedule,
	),
)
Output:

func WithLocation

func WithLocation(location *time.Location) SchedulerOption

WithLocation sets the location (i.e. timezone) that the scheduler should operate within. In many systems time.Local is UTC. Default: time.Local

Example
location, _ := time.LoadLocation("Asia/Kolkata")

_, _ = NewScheduler(
	WithLocation(location),
)
Output:

func WithLogger

func WithLogger(logger Logger) SchedulerOption

WithLogger sets the logger to be used by the Scheduler.

Example
_, _ = NewScheduler(
	WithLogger(
		NewLogger(LogLevelDebug),
	),
)
Output:

func WithMonitor added in v2.2.0

func WithMonitor(monitor Monitor) SchedulerOption

WithMonitor sets the metrics provider to be used by the Scheduler.

Example
//type exampleMonitor struct {
//	mu      sync.Mutex
//	counter map[string]int
//	time    map[string][]time.Duration
//}
//
//func newExampleMonitor() *exampleMonitor {
//	return &exampleMonitor{
//	counter: make(map[string]int),
//	time:    make(map[string][]time.Duration),
//}
//}
//
//func (t *exampleMonitor) IncrementJob(_ uuid.UUID, name string, _ []string, _ JobStatus) {
//	t.mu.Lock()
//	defer t.mu.Unlock()
//	_, ok := t.counter[name]
//	if !ok {
//		t.counter[name] = 0
//	}
//	t.counter[name]++
//}
//
//func (t *exampleMonitor) RecordJobTiming(startTime, endTime time.Time, _ uuid.UUID, name string, _ []string) {
//	t.mu.Lock()
//	defer t.mu.Unlock()
//	_, ok := t.time[name]
//	if !ok {
//		t.time[name] = make([]time.Duration, 0)
//	}
//	t.time[name] = append(t.time[name], endTime.Sub(startTime))
//}
//
//monitor := newExampleMonitor()
//s, _ := NewScheduler(
//	WithMonitor(monitor),
//)
//name := "example"
//_, _ = s.NewJob(
//	DurationJob(
//		time.Second,
//	),
//	NewTask(
//		func() {
//			time.Sleep(1 * time.Second)
//		},
//	),
//	WithName(name),
//	WithStartAt(
//		WithStartImmediately(),
//	),
//)
//s.Start()
//time.Sleep(5 * time.Second)
//_ = s.Shutdown()
//
//fmt.Printf("Job %q total execute count: %d\n", name, monitor.counter[name])
//for i, val := range monitor.time[name] {
//	fmt.Printf("Job %q execute #%d elapsed %.4f seconds\n", name, i+1, val.Seconds())
//}
Output:

func WithStopTimeout

func WithStopTimeout(timeout time.Duration) SchedulerOption

WithStopTimeout sets the amount of time the Scheduler should wait gracefully for jobs to complete before returning when StopJobs() or Shutdown() are called. Default: 10 * time.Second

Example
_, _ = NewScheduler(
	WithStopTimeout(time.Second * 5),
)
Output:

type StartAtOption

type StartAtOption func(*internalJob, time.Time) error

StartAtOption defines options for starting the job

func WithStartDateTime

func WithStartDateTime(start time.Time) StartAtOption

WithStartDateTime sets the first date & time at which the job should run. This datetime must be in the future.

func WithStartImmediately

func WithStartImmediately() StartAtOption

WithStartImmediately tells the scheduler to run the job immediately regardless of the type or schedule of job. After this immediate run the job is scheduled from this time based on the job definition.

type StopAtOption added in v2.11.0

type StopAtOption func(*internalJob, time.Time) error

StopAtOption defines options for stopping the job

func WithStopDateTime added in v2.11.0

func WithStopDateTime(end time.Time) StopAtOption

WithStopDateTime sets the final date & time after which the job should stop. This must be in the future and should be after the startTime (if specified). The job's final run may be at the stop time, but not after.

type Task

type Task func() task

Task defines a function that returns the task function and parameters.

func NewTask

func NewTask(function any, parameters ...any) Task

NewTask provides the job's task function and parameters.

type Weekdays

type Weekdays func() []time.Weekday

Weekdays defines a function that returns a list of week days.

func NewWeekdays

func NewWeekdays(weekday time.Weekday, weekdays ...time.Weekday) Weekdays

NewWeekdays provide the days of the week the job should run.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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