Update deps

This commit is contained in:
eleksir
2025-04-07 20:41:57 +03:00
parent 4fd0815c1d
commit 30cb930a5e
99 changed files with 2983 additions and 2434 deletions

View File

@@ -6,23 +6,25 @@ import (
"errors"
"fmt"
"math/rand"
"slices"
"strings"
"time"
"github.com/google/uuid"
"github.com/jonboulle/clockwork"
"github.com/robfig/cron/v3"
"golang.org/x/exp/slices"
)
// internalJob stores the information needed by the scheduler
// to manage scheduling, starting and stopping the job
type internalJob struct {
ctx context.Context
cancel context.CancelFunc
id uuid.UUID
name string
tags []string
ctx context.Context
parentCtx context.Context
cancel context.CancelFunc
id uuid.UUID
name string
tags []string
cron Cron
jobSchedule
// as some jobs may queue up, it's possible to
@@ -40,11 +42,13 @@ type internalJob struct {
startImmediately bool
stopTime time.Time
// event listeners
afterJobRuns func(jobID uuid.UUID, jobName string)
beforeJobRuns func(jobID uuid.UUID, jobName string)
afterJobRunsWithError func(jobID uuid.UUID, jobName string, err error)
afterJobRunsWithPanic func(jobID uuid.UUID, jobName string, recoverData any)
afterLockError func(jobID uuid.UUID, jobName string, err error)
afterJobRuns func(jobID uuid.UUID, jobName string)
beforeJobRuns func(jobID uuid.UUID, jobName string)
beforeJobRunsSkipIfBeforeFuncErrors func(jobID uuid.UUID, jobName string) error
afterJobRunsWithError func(jobID uuid.UUID, jobName string, err error)
afterJobRunsWithPanic func(jobID uuid.UUID, jobName string, recoverData any)
afterLockError func(jobID uuid.UUID, jobName string, err error)
disabledLocker bool
locker Locker
}
@@ -80,6 +84,10 @@ type task struct {
type Task func() task
// NewTask provides the job's task function and parameters.
// If you set the first argument of your Task func to be a context.Context,
// gocron will pass in a context (either the default Job context, or one
// provided via WithContext) to the job and will cancel the context on shutdown.
// This allows you to listen for and handle cancellation within your job.
func NewTask(function any, parameters ...any) Task {
return func() task {
return task{
@@ -97,6 +105,20 @@ type limitRunsTo struct {
runCount uint
}
// -----------------------------------------------
// -----------------------------------------------
// --------------- Custom Cron -------------------
// -----------------------------------------------
// -----------------------------------------------
// Cron defines the interface that must be
// implemented to provide a custom cron implementation for
// the job. Pass in the implementation using the JobOption WithCronImplementation.
type Cron interface {
IsValid(crontab string, location *time.Location, now time.Time) error
Next(lastRun time.Time) time.Time
}
// -----------------------------------------------
// -----------------------------------------------
// --------------- Job Variants ------------------
@@ -109,21 +131,29 @@ type JobDefinition interface {
setup(j *internalJob, l *time.Location, now time.Time) error
}
var _ JobDefinition = (*cronJobDefinition)(nil)
// Default cron implementation
type cronJobDefinition struct {
crontab string
withSeconds bool
func newDefaultCronImplementation(withSeconds bool) Cron {
return &defaultCron{
withSeconds: withSeconds,
}
}
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error {
var _ Cron = (*defaultCron)(nil)
type defaultCron struct {
cronSchedule cron.Schedule
withSeconds bool
}
func (c *defaultCron) IsValid(crontab string, location *time.Location, now time.Time) error {
var withLocation string
if strings.HasPrefix(c.crontab, "TZ=") || strings.HasPrefix(c.crontab, "CRON_TZ=") {
withLocation = c.crontab
if strings.HasPrefix(crontab, "TZ=") || strings.HasPrefix(crontab, "CRON_TZ=") {
withLocation = crontab
} else {
// since the user didn't provide a timezone default to the location
// passed in by the scheduler. Default: time.Local
withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), c.crontab)
withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), crontab)
}
var (
@@ -143,8 +173,32 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
if cronSchedule.Next(now).IsZero() {
return ErrCronJobInvalid
}
c.cronSchedule = cronSchedule
return nil
}
j.jobSchedule = &cronJob{cronSchedule: cronSchedule}
func (c *defaultCron) Next(lastRun time.Time) time.Time {
return c.cronSchedule.Next(lastRun)
}
// default cron job implementation
var _ JobDefinition = (*cronJobDefinition)(nil)
type cronJobDefinition struct {
crontab string
cron Cron
}
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error {
if j.cron != nil {
c.cron = j.cron
}
if err := c.cron.IsValid(c.crontab, location, now); err != nil {
return err
}
j.jobSchedule = &cronJob{crontab: c.crontab, cronSchedule: c.cron}
return nil
}
@@ -156,8 +210,8 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
// `CRON_TZ=America/Chicago * * * * *`
func CronJob(crontab string, withSeconds bool) JobDefinition {
return cronJobDefinition{
crontab: crontab,
withSeconds: withSeconds,
crontab: crontab,
cron: newDefaultCronImplementation(withSeconds),
}
}
@@ -252,6 +306,10 @@ func (d dailyJobDefinition) setup(j *internalJob, location *time.Location, _ tim
return ErrDailyJobMinutesSeconds
}
if d.interval == 0 {
return ErrDailyJobZeroInterval
}
ds := dailyJob{
interval: d.interval,
atTimes: atTimesDate,
@@ -270,6 +328,9 @@ type weeklyJobDefinition struct {
func (w weeklyJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
var ws weeklyJob
if w.interval == 0 {
return ErrWeeklyJobZeroInterval
}
ws.interval = w.interval
if w.daysOfTheWeek == nil {
@@ -304,8 +365,7 @@ type Weekdays func() []time.Weekday
// NewWeekdays provide the days of the week the job should run.
func NewWeekdays(weekday time.Weekday, weekdays ...time.Weekday) Weekdays {
return func() []time.Weekday {
weekdays = append(weekdays, weekday)
return weekdays
return append([]time.Weekday{weekday}, weekdays...)
}
}
@@ -335,6 +395,9 @@ type monthlyJobDefinition struct {
func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
var ms monthlyJob
if m.interval == 0 {
return ErrMonthlyJobZeroInterval
}
ms.interval = m.interval
if m.daysOfTheMonth == nil {
@@ -353,11 +416,9 @@ func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location, _ t
}
}
daysStart = removeSliceDuplicatesInt(daysStart)
slices.Sort(daysStart)
ms.days = daysStart
daysEnd = removeSliceDuplicatesInt(daysEnd)
slices.Sort(daysEnd)
ms.daysFromEnd = daysEnd
atTimesDate, err := convertAtTimesToDateTime(m.atTimes, location)
@@ -390,8 +451,7 @@ type DaysOfTheMonth func() days
// -5 == 5 days before the end of the month.
func NewDaysOfTheMonth(day int, moreDays ...int) DaysOfTheMonth {
return func() days {
moreDays = append(moreDays, day)
return moreDays
return append([]int{day}, moreDays...)
}
}
@@ -403,6 +463,14 @@ func (a atTime) time(location *time.Location) time.Time {
return time.Date(0, 0, 0, int(a.hours), int(a.minutes), int(a.seconds), 0, location)
}
// TimeFromAtTime is a helper function to allow converting AtTime into a time.Time value
// Note: the time.Time value will have zero values for all Time fields except Hours, Minutes, Seconds.
//
// For example: time.Date(0, 0, 0, 1, 1, 1, 0, time.UTC)
func TimeFromAtTime(at AtTime, loc *time.Location) time.Time {
return at().time(loc)
}
// AtTime defines a function that returns the internal atTime
type AtTime func() atTime
@@ -421,8 +489,7 @@ type AtTimes func() []AtTime
// the job should be run
func NewAtTimes(atTime AtTime, atTimes ...AtTime) AtTimes {
return func() []AtTime {
atTimes = append(atTimes, atTime)
return atTimes
return append([]AtTime{atTime}, atTimes...)
}
}
@@ -541,6 +608,16 @@ func WithDistributedJobLocker(locker Locker) JobOption {
}
}
// WithDisabledDistributedJobLocker disables the distributed job locker.
// This is useful when a global distributed locker has been set on the scheduler
// level using WithDistributedLocker and need to be disabled for specific jobs.
func WithDisabledDistributedJobLocker(disabled bool) JobOption {
return func(j *internalJob, _ time.Time) error {
j.disabledLocker = disabled
return nil
}
}
// WithEventListeners sets the event listeners that should be
// run for the job.
func WithEventListeners(eventListeners ...EventListener) JobOption {
@@ -578,6 +655,15 @@ func WithName(name string) JobOption {
}
}
// WithCronImplementation sets the custom Cron implementation for the job.
// This is only utilized for the CronJob type.
func WithCronImplementation(c Cron) JobOption {
return func(j *internalJob, _ time.Time) error {
j.cron = c
return nil
}
}
// 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.
@@ -676,6 +762,22 @@ func WithIdentifier(id uuid.UUID) JobOption {
}
}
// WithContext sets the parent context for the job.
// If you set the first argument of your Task func to be a context.Context,
// gocron will pass in the provided context to the job and will cancel the
// context on shutdown. If you cancel the context the job will no longer be
// scheduled as well. This allows you to both control the job via a context
// and listen for and handle cancellation within your job.
func WithContext(ctx context.Context) JobOption {
return func(j *internalJob, _ time.Time) error {
if ctx == nil {
return ErrWithContextNil
}
j.parentCtx = ctx
return nil
}
}
// -----------------------------------------------
// -----------------------------------------------
// ------------- Job Event Listeners -------------
@@ -698,6 +800,19 @@ func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) Even
}
}
// BeforeJobRunsSkipIfBeforeFuncErrors is used to listen for when a job is about to run and
// then runs the provided function. If the provided function returns an error, the job will be
// rescheduled and the current run will be skipped.
func BeforeJobRunsSkipIfBeforeFuncErrors(eventListenerFunc func(jobID uuid.UUID, jobName string) error) EventListener {
return func(j *internalJob) error {
if eventListenerFunc == nil {
return ErrEventListenerFuncNil
}
j.beforeJobRunsSkipIfBeforeFuncErrors = eventListenerFunc
return nil
}
}
// AfterJobRuns is used to listen for when a job has run
// without an error, and then run the provided function.
func AfterJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener {
@@ -759,7 +874,8 @@ type jobSchedule interface {
var _ jobSchedule = (*cronJob)(nil)
type cronJob struct {
cronSchedule cron.Schedule
crontab string
cronSchedule Cron
}
func (j *cronJob) next(lastRun time.Time) time.Time {
@@ -803,7 +919,7 @@ func (d dailyJob) next(lastRun time.Time) time.Time {
}
firstPass = false
startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, lastRun.Nanosecond(), lastRun.Location())
startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, 0, lastRun.Location())
return d.nextDay(startNextDay, firstPass)
}
@@ -811,7 +927,7 @@ func (d dailyJob) nextDay(lastRun time.Time, firstPass bool) time.Time {
for _, at := range d.atTimes {
// sub the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if firstPass && atDate.After(lastRun) {
// checking to see if it is after i.e. greater than,
@@ -857,7 +973,7 @@ func (w weeklyJob) nextWeekDayAtTime(lastRun time.Time, firstPass bool) time.Tim
for _, at := range w.atTimes {
// sub the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if firstPass && atDate.After(lastRun) {
// checking to see if it is after i.e. greater than,
@@ -925,7 +1041,7 @@ func (m monthlyJob) nextMonthDayAtTime(lastRun time.Time, days []int, firstPass
for _, at := range m.atTimes {
// sub the day, and the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if atDate.Month() != lastRun.Month() {
// this check handles if we're setting a day not in the current month
@@ -1089,12 +1205,14 @@ func (j job) RunNow() error {
defer cancel()
resp := make(chan error, 1)
t := time.NewTimer(100 * time.Millisecond)
select {
case j.runJobRequest <- runJobRequest{
id: j.id,
outChan: resp,
}:
case <-time.After(100 * time.Millisecond):
t.Stop()
case <-t.C:
return ErrJobRunNowFailed
}
var err error