From 5974e26ddef24b60084887b2e710898055a10699 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 02:47:46 +0000 Subject: [PATCH] Bump github.com/go-co-op/gocron from 1.31.2 to 1.36.0 Bumps [github.com/go-co-op/gocron](https://github.com/go-co-op/gocron) from 1.31.2 to 1.36.0. - [Release notes](https://github.com/go-co-op/gocron/releases) - [Commits](https://github.com/go-co-op/gocron/compare/v1.31.2...v1.36.0) --- updated-dependencies: - dependency-name: github.com/go-co-op/gocron dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 8 +- vendor/github.com/go-co-op/gocron/README.md | 17 + vendor/github.com/go-co-op/gocron/executor.go | 37 +- vendor/github.com/go-co-op/gocron/gocron.go | 12 +- vendor/github.com/go-co-op/gocron/job.go | 51 ++- vendor/github.com/go-co-op/gocron/locker.go | 7 + .../github.com/go-co-op/gocron/scheduler.go | 327 +++++++++++------- vendor/github.com/google/uuid/CHANGELOG.md | 11 + vendor/github.com/google/uuid/CONTRIBUTING.md | 2 +- vendor/github.com/google/uuid/uuid.go | 26 +- vendor/gorm.io/gorm/{License => LICENSE} | 0 vendor/modules.txt | 4 +- 13 files changed, 342 insertions(+), 164 deletions(-) rename vendor/gorm.io/gorm/{License => LICENSE} (100%) diff --git a/go.mod b/go.mod index 09ee19f48..8bcbfefd3 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/99designs/gqlgen v0.17.36 github.com/bxcodec/faker/v3 v3.8.1 github.com/bxcodec/faker/v4 v4.0.0-beta.3 - github.com/go-co-op/gocron v1.31.2 + github.com/go-co-op/gocron v1.36.0 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git/v5 v5.8.1 github.com/go-playground/validator/v10 v10.15.0 @@ -17,7 +17,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-queue/contrib v0.0.1 github.com/golang-queue/queue v0.1.3 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.4.0 github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.16.7 github.com/lestrrat-go/jwx v1.2.26 diff --git a/go.sum b/go.sum index 6a44baff2..c932da3b0 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/go-co-op/gocron v1.31.2 h1:tAUW64bxYc5QlzEy2t30TnHX2+uInNDajKXxWi4SACA= -github.com/go-co-op/gocron v1.31.2/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= +github.com/go-co-op/gocron v1.36.0 h1:sEmAwg57l4JWQgzaVWYfKZ+w13uHOqeOtwjo72Ll5Wc= +github.com/go-co-op/gocron v1.36.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= @@ -105,8 +105,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= diff --git a/vendor/github.com/go-co-op/gocron/README.md b/vendor/github.com/go-co-op/gocron/README.md index 59f491906..33815c193 100644 --- a/vendor/github.com/go-co-op/gocron/README.md +++ b/vendor/github.com/go-co-op/gocron/README.md @@ -1,5 +1,6 @@ # gocron: A Golang Job Scheduling Package. +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#job-scheduler) [![CI State](https://github.com/go-co-op/gocron/actions/workflows/go_test.yml/badge.svg?branch=main&event=push)](https://github.com/go-co-op/gocron/actions) ![Go Report Card](https://goreportcard.com/badge/github.com/go-co-op/gocron) [![Go Doc](https://godoc.org/github.com/go-co-op/gocron?status.svg)](https://pkg.go.dev/github.com/go-co-op/gocron) @@ -39,6 +40,14 @@ if err != nil { // handle the error related to setting up the job } +// to wait for the interval to pass before running the first job +// use WaitForSchedule or WaitForScheduleAll +s.Every(5).Second().WaitForSchedule().Do(func(){ ... }) + +s.WaitForScheduleAll() +s.Every(5).Second().Do(func(){ ... }) // waits for schedule +s.Every(5).Second().Do(func(){ ... }) // waits for schedule + // strings parse to duration s.Every("5m").Do(func(){ ... }) @@ -71,6 +80,13 @@ s.CronWithSeconds("*/1 * * * * *").Do(task) // every second s.StartAsync() // starts the scheduler and blocks current execution path s.StartBlocking() + +// stop the running scheduler in two different ways: +// stop the scheduler +s.Stop() + +// stop the scheduler and notify the `StartBlocking()` to exit +s.StopBlockingChan() ``` For more examples, take a look in our [go docs](https://pkg.go.dev/github.com/go-co-op/gocron#pkg-examples) @@ -96,6 +112,7 @@ There are several options available to restrict how jobs run: | Job singleton | `SingletonMode()` | a long running job will not be rescheduled until the current run is completed | | Scheduler limit | `SetMaxConcurrentJobs()` | set a collective maximum number of concurrent jobs running across the scheduler | | Distributed locking | `WithDistributedLocker()` | prevents the same job from being run more than once when running multiple instances of the scheduler | +| Distributed elector | `WithDistributedElector()` | multiple instances exist in a distributed scenario, only the leader instance can run jobs | ## Distributed Locker Implementations diff --git a/vendor/github.com/go-co-op/gocron/executor.go b/vendor/github.com/go-co-op/gocron/executor.go index 401f7fcb9..894468c4c 100644 --- a/vendor/github.com/go-co-op/gocron/executor.go +++ b/vendor/github.com/go-co-op/gocron/executor.go @@ -32,7 +32,6 @@ const ( // // blocked trying to send to the buffered channel // time.Sleep(10 * time.Minute) // }) - WaitMode ) @@ -54,7 +53,8 @@ type executor struct { limitModeRunningJobs *atomic.Int64 // tracks the count of running jobs to check against the max stopped *atomic.Bool // allow workers to drain the buffered limitModeQueue - distributedLocker Locker // support running jobs across multiple instances + distributedLocker Locker // support running jobs across multiple instances + distributedElector Elector // support running jobs across multiple instances } func newExecutor() executor { @@ -128,7 +128,11 @@ func (e *executor) limitModeRunner() { return case jf := <-e.limitModeQueue: if !e.stopped.Load() { - e.runJob(jf) + select { + case <-jf.ctx.Done(): + default: + e.runJob(jf) + } } } } @@ -154,12 +158,25 @@ func (e *executor) start() { } func (e *executor) runJob(f jobFunction) { + defer func() { + if e.limitMode == RescheduleMode && e.limitModeMaxRunningJobs > 0 { + e.limitModeRunningJobs.Add(-1) + } + }() switch f.runConfig.mode { case defaultMode: lockKey := f.jobName if lockKey == "" { lockKey = f.funcName } + if e.distributedElector != nil { + err := e.distributedElector.IsLeader(e.ctx) + if err != nil { + return + } + runJob(f) + return + } if e.distributedLocker != nil { l, err := e.distributedLocker.Lock(f.ctx, lockKey) if err != nil || l == nil { @@ -170,8 +187,17 @@ func (e *executor) runJob(f jobFunction) { if durationToNextRun > time.Second*5 { durationToNextRun = time.Second * 5 } + + delay := time.Duration(float64(durationToNextRun) * 0.9) + if e.limitModeMaxRunningJobs > 0 { + time.AfterFunc(delay, func() { + _ = l.Unlock(f.ctx) + }) + return + } + if durationToNextRun > time.Millisecond*100 { - timer := time.NewTimer(time.Duration(float64(durationToNextRun) * 0.9)) + timer := time.NewTimer(delay) defer timer.Stop() select { @@ -181,6 +207,8 @@ func (e *executor) runJob(f jobFunction) { } _ = l.Unlock(f.ctx) }() + runJob(f) + return } runJob(f) case singletonMode: @@ -225,6 +253,7 @@ func (e *executor) run() { if e.limitModeRunningJobs.Load() < int64(e.limitModeMaxRunningJobs) { select { case e.limitModeQueue <- f: + e.limitModeRunningJobs.Inc() case <-e.ctx.Done(): } } diff --git a/vendor/github.com/go-co-op/gocron/gocron.go b/vendor/github.com/go-co-op/gocron/gocron.go index cb667022a..a15841736 100644 --- a/vendor/github.com/go-co-op/gocron/gocron.go +++ b/vendor/github.com/go-co-op/gocron/gocron.go @@ -40,6 +40,7 @@ var ( ErrNotAFunction = errors.New("gocron: only functions can be scheduled into the job queue") ErrNotScheduledWeekday = errors.New("gocron: job not scheduled weekly on a weekday") ErrJobNotFoundWithTag = errors.New("gocron: no jobs found with given tag") + ErrJobNotFound = errors.New("gocron: no job found") ErrUnsupportedTimeFormat = errors.New("gocron: the given time format is not supported") ErrInvalidInterval = errors.New("gocron: .Every() interval must be greater than 0") ErrInvalidIntervalType = errors.New("gocron: .Every() interval must be int, time.Duration, or string") @@ -48,7 +49,8 @@ var ( ErrAtTimeNotSupported = errors.New("gocron: the At() method is not supported for this time unit") ErrWeekdayNotSupported = errors.New("gocron: weekday is not supported for time unit") - ErrInvalidDayOfMonthEntry = errors.New("gocron: only days 1 through 28 are allowed for monthly schedules") + ErrInvalidDayOfMonthEntry = errors.New("gocron: only days 1 through 28 and -1 through -28 are allowed for monthly schedules") + ErrInvalidMonthLastDayEntry = errors.New("gocron: only a single negative integer is permitted for MonthLastDay") ErrTagsUnique = func(tag string) error { return fmt.Errorf("gocron: a non-unique tag was set on the job: %s", tag) } ErrWrongParams = errors.New("gocron: wrong list of params") ErrDoWithJobDetails = errors.New("gocron: DoWithJobDetails expects a function whose last parameter is a gocron.Job") @@ -88,6 +90,10 @@ const ( crontab ) +func (s schedulingUnit) String() string { + return [...]string{"milliseconds", "seconds", "minutes", "hours", "days", "weeks", "months", "duration", "crontab"}[s] +} + func callJobFunc(jobFunc interface{}) { if jobFunc == nil { return @@ -134,9 +140,9 @@ func getFunctionNameOfPointer(fn interface{}) string { func parseTime(t string) (hour, min, sec int, err error) { var timeLayout string switch { - case timeWithSeconds.Match([]byte(t)): + case timeWithSeconds.MatchString(t): timeLayout = "15:04:05" - case timeWithoutSeconds.Match([]byte(t)): + case timeWithoutSeconds.MatchString(t): timeLayout = "15:04" default: return 0, 0, 0, ErrUnsupportedTimeFormat diff --git a/vendor/github.com/go-co-op/gocron/job.go b/vendor/github.com/go-co-op/gocron/job.go index d72a0f118..76ab356a6 100644 --- a/vendor/github.com/go-co-op/gocron/job.go +++ b/vendor/github.com/go-co-op/gocron/job.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/google/uuid" "github.com/robfig/cron/v3" "go.uber.org/atomic" ) @@ -27,7 +28,7 @@ type Job struct { scheduledWeekdays []time.Weekday // Specific days of the week to start on daysOfTheMonth []int // Specific days of the month to run the job - tags []string // allow the user to tag Jobs with certain labels + tags []string // allow the user to tag jobs with certain labels timer *time.Timer // handles running tasks at specific time cronSchedule cron.Schedule // stores the schedule when a task uses cron runWithDetails bool // when true the job is passed as the last arg of the jobFunc @@ -47,6 +48,7 @@ type random struct { } type jobFunction struct { + id uuid.UUID // unique identifier for the job *jobRunTimes // tracking all the markers for job run times eventListeners // additional functions to allow run 'em during job performing function interface{} // task's function @@ -84,6 +86,7 @@ type jobMutex struct { func (jf *jobFunction) copy() jobFunction { cp := jobFunction{ + id: jf.id, jobRunTimes: jf.jobRunTimes, eventListeners: jf.eventListeners, function: jf.function, @@ -141,6 +144,7 @@ func newJob(interval int, startImmediately bool, singletonMode bool) *Job { interval: interval, unit: seconds, jobFunction: jobFunction{ + id: uuid.New(), jobRunTimes: &jobRunTimes{ jobRunTimesMu: &sync.Mutex{}, lastRun: time.Time{}, @@ -173,6 +177,15 @@ func (j *Job) Name(name string) { j.jobName = name } +// GetName returns the name of the current job. +// The name is either the name set using Job.Name() / Scheduler.Name() or +// the name of the funcion as Go sees it, for example `main.func1` +func (j *Job) GetName() string { + j.mu.Lock() + defer j.mu.Unlock() + return j.jobFunction.getName() +} + func (j *Job) setRandomInterval(a, b int) { j.random.rand = rand.New(rand.NewSource(time.Now().UnixNano())) // nolint @@ -227,24 +240,21 @@ func (j *Job) getFirstAtTime() time.Duration { } func (j *Job) getAtTime(lastRun time.Time) time.Duration { - var r time.Duration if len(j.atTimes) == 0 { - return r + return 0 } - if len(j.atTimes) == 1 { - return j.atTimes[0] + r := j.atTimes[0] + + if len(j.atTimes) == 1 || lastRun.IsZero() { + return r } - if lastRun.IsZero() { - r = j.atTimes[0] - } else { - for _, d := range j.atTimes { - nt := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, lastRun.Location()).Add(d) - if nt.After(lastRun) { - r = d - break - } + for _, d := range j.atTimes { + nt := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, lastRun.Location()).Add(d) + if nt.After(lastRun) { + r = d + break } } @@ -436,6 +446,16 @@ func (j *Job) ScheduledTime() time.Time { return j.nextRun } +// ScheduledUnit returns the scheduled unit of the Job. +func (j *Job) ScheduledUnit() string { + return j.unit.String() +} + +// Interval returns the scheduled interval of the Job. +func (j *Job) ScheduledInterval() int { + return j.interval +} + // ScheduledAtTime returns the specific time of day the Job will run at. // If multiple times are set, the earliest time will be returned. func (j *Job) ScheduledAtTime() string { @@ -472,6 +492,9 @@ func (j *Job) Weekdays() []time.Weekday { if len(j.scheduledWeekdays) == 0 { return []time.Weekday{time.Sunday} } + sort.Slice(j.scheduledWeekdays, func(i, k int) bool { + return j.scheduledWeekdays[i] < j.scheduledWeekdays[k] + }) return j.scheduledWeekdays } diff --git a/vendor/github.com/go-co-op/gocron/locker.go b/vendor/github.com/go-co-op/gocron/locker.go index dc713f9b3..193b20c70 100644 --- a/vendor/github.com/go-co-op/gocron/locker.go +++ b/vendor/github.com/go-co-op/gocron/locker.go @@ -21,3 +21,10 @@ type Locker interface { type Lock interface { Unlock(ctx 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 Elector interface { + // IsLeader should return an error if the job should not be scheduled and nil if the job should be scheduled. + IsLeader(ctx context.Context) error +} diff --git a/vendor/github.com/go-co-op/gocron/scheduler.go b/vendor/github.com/go-co-op/gocron/scheduler.go index 5d1b0cf74..f51e0bde8 100644 --- a/vendor/github.com/go-co-op/gocron/scheduler.go +++ b/vendor/github.com/go-co-op/gocron/scheduler.go @@ -9,17 +9,17 @@ import ( "sync" "time" + "github.com/google/uuid" "github.com/robfig/cron/v3" "go.uber.org/atomic" ) type limitMode int8 -// Scheduler struct stores a list of Jobs and the location of time used by the Scheduler, -// and implements the sort. any for sorting Jobs, by the time of jobFuncNextRun +// Scheduler struct stores a list of Jobs and the location of time used by the Scheduler type Scheduler struct { jobsMutex sync.RWMutex - jobs []*Job + jobs map[uuid.UUID]*Job locationMutex sync.RWMutex location *time.Location @@ -45,7 +45,7 @@ type Scheduler struct { // Update() calls Do(), so really they all end with Do(). // This allows the caller to begin with any job related scheduler method // and only with one of [ Every(), EveryRandom(), Cron(), CronWithSeconds(), MonthFirstWeekday() ] - inScheduleChain bool + inScheduleChain *uuid.UUID } // days in a week @@ -55,8 +55,7 @@ const allWeekDays = 7 func NewScheduler(loc *time.Location) *Scheduler { executor := newExecutor() - return &Scheduler{ - jobs: make([]*Job, 0), + s := &Scheduler{ location: loc, running: atomic.NewBool(false), time: &trueTime{}, @@ -64,6 +63,10 @@ func NewScheduler(loc *time.Location) *Scheduler { tagsUnique: false, timer: afterFunc, } + s.jobsMutex.Lock() + s.jobs = map[uuid.UUID]*Job{} + s.jobsMutex.Unlock() + return s } // SetMaxConcurrentJobs limits how many jobs can be running at the same time. @@ -101,11 +104,13 @@ func (s *Scheduler) StartAsync() { func (s *Scheduler) start() { s.executor.start() s.setRunning(true) - s.runJobs(s.Jobs()) + s.runJobs() } -func (s *Scheduler) runJobs(jobs []*Job) { - for _, job := range jobs { +func (s *Scheduler) runJobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { ctx, cancel := context.WithCancel(context.Background()) job.mu.Lock() job.ctx = ctx @@ -124,11 +129,28 @@ func (s *Scheduler) IsRunning() bool { return s.running.Load() } -// Jobs returns the list of Jobs from the Scheduler +// Jobs returns the list of Jobs from the scheduler func (s *Scheduler) Jobs() []*Job { s.jobsMutex.RLock() defer s.jobsMutex.RUnlock() - return s.jobs + jobs := make([]*Job, len(s.jobs)) + var counter int + for _, job := range s.jobs { + jobs[counter] = job + counter++ + } + return jobs +} + +// JobsMap returns a map of job uuid to job +func (s *Scheduler) JobsMap() map[uuid.UUID]*Job { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + jobs := make(map[uuid.UUID]*Job, len(s.jobs)) + for id, job := range s.jobs { + jobs[id] = job + } + return jobs } // Name sets the name of the current job. @@ -141,33 +163,13 @@ func (s *Scheduler) Name(name string) *Scheduler { return s } -func (s *Scheduler) setJobs(jobs []*Job) { - s.jobsMutex.Lock() - defer s.jobsMutex.Unlock() - s.jobs = jobs -} - -// Len returns the number of Jobs in the Scheduler - implemented for sort +// Len returns the number of Jobs in the Scheduler func (s *Scheduler) Len() int { s.jobsMutex.RLock() defer s.jobsMutex.RUnlock() return len(s.jobs) } -// Swap places each job into the other job's position given -// the provided job indexes. -func (s *Scheduler) Swap(i, j int) { - s.jobsMutex.Lock() - defer s.jobsMutex.Unlock() - s.jobs[i], s.jobs[j] = s.jobs[j], s.jobs[i] -} - -// Less compares the next run of jobs based on their index. -// Returns true if the second job is after the first. -func (s *Scheduler) Less(first, second int) bool { - return s.Jobs()[second].NextRun().Unix() >= s.Jobs()[first].NextRun().Unix() -} - // ChangeLocation changes the default time location func (s *Scheduler) ChangeLocation(newLocation *time.Location) { s.locationMutex.Lock() @@ -219,7 +221,7 @@ func (s *Scheduler) scheduleNextRun(job *Job) (bool, nextRun) { } if !job.shouldRun() { - s.RemoveByReference(job) + _ = s.RemoveByID(job) return false, nextRun{} } @@ -283,9 +285,9 @@ func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) nextRun { } func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) nextRun { - // Special case: the last day of the month - if len(job.daysOfTheMonth) == 1 && job.daysOfTheMonth[0] == -1 { - return calculateNextRunForLastDayOfMonth(s, job, lastRun) + // Special case: negative days from the end of the month + if len(job.daysOfTheMonth) == 1 && job.daysOfTheMonth[0] < 0 { + return calculateNextRunForLastDayOfMonth(s, job, lastRun, job.daysOfTheMonth[0]) } if len(job.daysOfTheMonth) != 0 { // calculate days to job.daysOfTheMonth @@ -309,13 +311,13 @@ func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) nextRun { return nextRun{duration: until(lastRun, next), dateTime: next} } -func calculateNextRunForLastDayOfMonth(s *Scheduler, job *Job, lastRun time.Time) nextRun { +func calculateNextRunForLastDayOfMonth(s *Scheduler, job *Job, lastRun time.Time, dayBeforeLastOfMonth int) nextRun { // Calculate the last day of the next month, by adding job.interval+1 months (i.e. the // first day of the month after the next month), and subtracting one day, unless the // last run occurred before the end of the month. addMonth := job.getInterval() atTime := job.getAtTime(lastRun) - if testDate := lastRun.AddDate(0, 0, 1); testDate.Month() != lastRun.Month() && + if testDate := lastRun.AddDate(0, 0, -dayBeforeLastOfMonth); testDate.Month() != lastRun.Month() && !s.roundToMidnightAndAddDSTAware(lastRun, atTime).After(lastRun) { // Our last run was on the last day of this month. addMonth++ @@ -325,7 +327,7 @@ func calculateNextRunForLastDayOfMonth(s *Scheduler, job *Job, lastRun time.Time next := time.Date(lastRun.Year(), lastRun.Month(), 1, 0, 0, 0, 0, s.Location()). Add(atTime). AddDate(0, addMonth, 0). - AddDate(0, 0, -1) + AddDate(0, 0, dayBeforeLastOfMonth) return nextRun{duration: until(lastRun, next), dateTime: next} } @@ -391,8 +393,8 @@ func (s *Scheduler) calculateWeeks(job *Job, lastRun time.Time) nextRun { func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekday int, job *Job) int { if job.getInterval() > 1 { - // just count weeks after the first jobs were done - if job.RunCount() < len(job.Weekdays()) { + weekDays := job.Weekdays() + if job.lastRun.Weekday() != weekDays[len(weekDays)-1] { return daysToWeekday } if daysToWeekday > 0 { @@ -412,15 +414,10 @@ func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekda } func (s *Scheduler) calculateDays(job *Job, lastRun time.Time) nextRun { - if job.getInterval() == 1 { - lastRunDayPlusJobAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)) - - if shouldRunToday(lastRun, lastRunDayPlusJobAtTime) { - return nextRun{duration: until(lastRun, lastRunDayPlusJobAtTime), dateTime: lastRunDayPlusJobAtTime} - } + nextRunAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)).In(s.Location()) + if s.now().After(nextRunAtTime) || s.now() == nextRunAtTime { + nextRunAtTime = nextRunAtTime.AddDate(0, 0, job.getInterval()) } - - nextRunAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getFirstAtTime()).AddDate(0, 0, job.getInterval()).In(s.Location()) return nextRun{duration: until(lastRun, nextRunAtTime), dateTime: nextRunAtTime} } @@ -428,10 +425,6 @@ func until(from time.Time, until time.Time) time.Duration { return until.Sub(from) } -func shouldRunToday(lastRun time.Time, atTime time.Time) bool { - return lastRun.Before(atTime) -} - func in(scheduleWeekdays []time.Weekday, weekday time.Weekday) bool { in := false @@ -511,13 +504,23 @@ func (s *Scheduler) roundToMidnightAndAddDSTAware(t time.Time, d time.Duration) // NextRun datetime when the next Job should run. func (s *Scheduler) NextRun() (*Job, time.Time) { - if len(s.Jobs()) <= 0 { - return nil, s.now() + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + if len(s.jobs) <= 0 { + return nil, time.Time{} } - sort.Sort(s) + var jobID uuid.UUID + var nearestRun time.Time + for _, job := range s.jobs { + nr := job.NextRun() + if (nr.Before(nearestRun) || nearestRun.IsZero()) && s.now().Before(nr) { + nearestRun = nr + jobID = job.id + } + } - return s.Jobs()[0], s.Jobs()[0].NextRun() + return s.jobs[jobID], nearestRun } // EveryRandom schedules a new period Job that runs at random intervals @@ -534,9 +537,13 @@ func (s *Scheduler) EveryRandom(lower, upper int) *Scheduler { // Every schedules a new periodic Job with an interval. // Interval can be an int, time.Duration or a string that // parses with time.ParseDuration(). +// Negative intervals will return an error. // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". // -// The job is run immediately, unless StartAt or At is set. +// The job is run immediately, unless: +// * StartAt or At is set on the job, +// * WaitForSchedule is set on the job, +// * or WaitForScheduleAll is set on the scheduler. func (s *Scheduler) Every(interval interface{}) *Scheduler { job := s.getCurrentJob() @@ -547,6 +554,9 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler { job.error = wrapOrError(job.error, ErrInvalidInterval) } case time.Duration: + if interval <= 0 { + job.error = wrapOrError(job.error, ErrInvalidInterval) + } job.setInterval(0) job.setDuration(interval) job.setUnit(duration) @@ -555,6 +565,9 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler { if err != nil { job.error = wrapOrError(job.error, err) } + if d <= 0 { + job.error = wrapOrError(job.error, ErrInvalidInterval) + } job.setDuration(d) job.setUnit(duration) default: @@ -638,22 +651,24 @@ func (s *Scheduler) RunAll() { s.RunAllWithDelay(0) } -// RunAllWithDelay runs all jobs with the provided delay in between each job +// RunAllWithDelay runs all Jobs with the provided delay in between each Job func (s *Scheduler) RunAllWithDelay(d time.Duration) { - for _, job := range s.Jobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { s.run(job) s.time.Sleep(d) } } -// RunByTag runs all the jobs containing a specific tag +// RunByTag runs all the Jobs containing a specific tag // regardless of whether they are scheduled to run or not func (s *Scheduler) RunByTag(tag string) error { return s.RunByTagWithDelay(tag, 0) } // RunByTagWithDelay is same as RunByTag but introduces a delay between -// each job execution +// each Job execution func (s *Scheduler) RunByTagWithDelay(tag string, d time.Duration) error { jobs, err := s.FindJobsByTag(tag) if err != nil { @@ -688,16 +703,13 @@ func (s *Scheduler) Remove(job interface{}) { // RemoveByReference removes specific Job by reference func (s *Scheduler) RemoveByReference(job *Job) { - s.removeJobsUniqueTags(job) - s.removeByCondition(func(someJob *Job) bool { - job.mu.RLock() - defer job.mu.RUnlock() - return someJob == job - }) + _ = s.RemoveByID(job) } func (s *Scheduler) findJobByTaskName(name string) *Job { - for _, job := range s.Jobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { if job.funcName == name { return job } @@ -717,23 +729,31 @@ func (s *Scheduler) removeJobsUniqueTags(job *Job) { } func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) { - retainedJobs := make([]*Job, 0) - for _, job := range s.Jobs() { - if !shouldRemove(job) { - retainedJobs = append(retainedJobs, job) - } else { - job.stop() + s.jobsMutex.Lock() + defer s.jobsMutex.Unlock() + for _, job := range s.jobs { + if shouldRemove(job) { + s.stopJob(job) + delete(s.jobs, job.id) } } - s.setJobs(retainedJobs) } -// RemoveByTag will remove Jobs that match the given tag. +func (s *Scheduler) stopJob(job *Job) { + job.mu.Lock() + if job.runConfig.mode == singletonMode { + s.executor.singletonWgs.Delete(job.singletonWg) + } + job.mu.Unlock() + job.stop() +} + +// RemoveByTag will remove jobs that match the given tag. func (s *Scheduler) RemoveByTag(tag string) error { return s.RemoveByTags(tag) } -// RemoveByTags will remove Jobs that match all given tags. +// RemoveByTags will remove jobs that match all given tags. func (s *Scheduler) RemoveByTags(tags ...string) error { jobs, err := s.FindJobsByTag(tags...) if err != nil { @@ -741,12 +761,12 @@ func (s *Scheduler) RemoveByTags(tags ...string) error { } for _, job := range jobs { - s.RemoveByReference(job) + _ = s.RemoveByID(job) } return nil } -// RemoveByTagsAny will remove Jobs that match any one of the given tags. +// RemoveByTagsAny will remove jobs that match any one of the given tags. func (s *Scheduler) RemoveByTagsAny(tags ...string) error { var errs error mJob := make(map[*Job]struct{}) @@ -761,18 +781,33 @@ func (s *Scheduler) RemoveByTagsAny(tags ...string) error { } for job := range mJob { - s.RemoveByReference(job) + _ = s.RemoveByID(job) } return errs } -// FindJobsByTag will return a slice of Jobs that match all given tags +// RemoveByID removes the job from the scheduler looking up by id +func (s *Scheduler) RemoveByID(job *Job) error { + s.jobsMutex.Lock() + defer s.jobsMutex.Unlock() + if _, ok := s.jobs[job.id]; ok { + s.removeJobsUniqueTags(job) + s.stopJob(job) + delete(s.jobs, job.id) + return nil + } + return ErrJobNotFound +} + +// FindJobsByTag will return a slice of jobs that match all given tags func (s *Scheduler) FindJobsByTag(tags ...string) ([]*Job, error) { var jobs []*Job + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() Jobs: - for _, job := range s.Jobs() { + for _, job := range s.jobs { if job.hasTags(tags...) { jobs = append(jobs, job) continue Jobs @@ -843,7 +878,9 @@ func (s *Scheduler) SingletonModeAll() { // TaskPresent checks if specific job's function was added to the scheduler. func (s *Scheduler) TaskPresent(j interface{}) bool { - for _, job := range s.Jobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { if job.funcName == getFunctionName(j) { return true } @@ -851,29 +888,21 @@ func (s *Scheduler) TaskPresent(j interface{}) bool { return false } -// To avoid the recursive read lock on s.Jobs() and this function, -// creating this new function and distributing the lock between jobPresent, _jobPresent -func (s *Scheduler) _jobPresent(j *Job, jobs []*Job) bool { +func (s *Scheduler) jobPresent(j *Job) bool { s.jobsMutex.RLock() defer s.jobsMutex.RUnlock() - for _, job := range jobs { - if job == j { - return true - } + if _, ok := s.jobs[j.id]; ok { + return true } return false } -func (s *Scheduler) jobPresent(j *Job) bool { - return s._jobPresent(j, s.Jobs()) -} - // Clear clears all Jobs from this scheduler func (s *Scheduler) Clear() { - for _, job := range s.Jobs() { - job.stop() - } - s.setJobs(make([]*Job, 0)) + s.stopJobs() + s.jobsMutex.Lock() + defer s.jobsMutex.Unlock() + s.jobs = make(map[uuid.UUID]*Job) // If unique tags was enabled, delete all the tags loaded in the tags sync.Map if s.tagsUnique { s.tags.Range(func(key interface{}, value interface{}) bool { @@ -908,7 +937,7 @@ func (s *Scheduler) stopJobs() { func (s *Scheduler) doCommon(jobFun interface{}, params ...interface{}) (*Job, error) { job := s.getCurrentJob() - s.inScheduleChain = false + s.inScheduleChain = nil jobUnit := job.getUnit() jobLastRun := job.LastRun() @@ -929,7 +958,7 @@ func (s *Scheduler) doCommon(jobFun interface{}, params ...interface{}) (*Job, e if job.error != nil { // delete the job from the scheduler as this job // cannot be executed - s.RemoveByReference(job) + _ = s.RemoveByID(job) return nil, job.error } @@ -940,7 +969,7 @@ func (s *Scheduler) doCommon(jobFun interface{}, params ...interface{}) (*Job, e if val.Kind() != reflect.Func { // delete the job for the same reason as above - s.RemoveByReference(job) + _ = s.RemoveByID(job) return nil, ErrNotAFunction } @@ -967,13 +996,13 @@ func (s *Scheduler) doCommon(jobFun interface{}, params ...interface{}) (*Job, e } if len(params) != expectedParamLength { - s.RemoveByReference(job) + _ = s.RemoveByID(job) job.error = wrapOrError(job.error, ErrWrongParams) return nil, job.error } if job.runWithDetails && val.Type().In(len(params)).Kind() != reflect.ValueOf(*job).Kind() { - s.RemoveByReference(job) + _ = s.RemoveByID(job) job.error = wrapOrError(job.error, ErrDoWithJobDetails) return nil, job.error } @@ -1004,6 +1033,11 @@ func (s *Scheduler) DoWithJobDetails(jobFun interface{}, params ...interface{}) // At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM" // or time.Time (note that only the hours, minutes, seconds and nanos are used). +// When the At time(s) occur on the same day on which the scheduler is started +// the Job will be run at the first available At time. +// For example: a schedule for every 2 days at 9am and 11am +// - currently 7am -> Job runs at 9am and 11am on the day the scheduler was started +// - currently 12 noon -> Job runs at 9am and 11am two days after the scheduler started func (s *Scheduler) At(i interface{}) *Scheduler { job := s.getCurrentJob() @@ -1048,7 +1082,9 @@ func (s *Scheduler) Tag(t ...string) *Scheduler { // GetAllTags returns all tags. func (s *Scheduler) GetAllTags() []string { var tags []string - for _, job := range s.Jobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { tags = append(tags, job.Tags()...) } return tags @@ -1074,12 +1110,12 @@ func (s *Scheduler) setUnit(unit schedulingUnit) { job.setUnit(unit) } -// Millisecond sets the unit with seconds +// Millisecond sets the unit with milliseconds func (s *Scheduler) Millisecond() *Scheduler { return s.Milliseconds() } -// Milliseconds sets the unit with seconds +// Milliseconds sets the unit with milliseconds func (s *Scheduler) Milliseconds() *Scheduler { s.setUnit(milliseconds) return s @@ -1120,8 +1156,7 @@ func (s *Scheduler) Hours() *Scheduler { // Day sets the unit with days func (s *Scheduler) Day() *Scheduler { - s.setUnit(days) - return s + return s.Days() } // Days set the unit with days @@ -1143,19 +1178,42 @@ func (s *Scheduler) Weeks() *Scheduler { } // Month sets the unit with months +// Note: Only days 1 through 28 are allowed for monthly schedules +// Note: Multiple of the same day of month is not allowed +// Note: Negative numbers are special values and can only occur as single argument +// and count backwards from the end of the month -1 == last day of the month, -2 == penultimate day of the month func (s *Scheduler) Month(daysOfMonth ...int) *Scheduler { return s.Months(daysOfMonth...) } // MonthLastDay sets the unit with months at every last day of the month -func (s *Scheduler) MonthLastDay() *Scheduler { - return s.Months(-1) +// The optional parameter is a negative integer denoting days previous to the +// last day of the month. E.g. -1 == the penultimate day of the month, +// -2 == two days for the last day of the month +func (s *Scheduler) MonthLastDay(dayCountBeforeLastDayOfMonth ...int) *Scheduler { + job := s.getCurrentJob() + + switch l := len(dayCountBeforeLastDayOfMonth); l { + case 0: + return s.Months(-1) + case 1: + count := dayCountBeforeLastDayOfMonth[0] + if count >= 0 { + job.error = wrapOrError(job.error, ErrInvalidMonthLastDayEntry) + return s + } + return s.Months(count - 1) + default: + job.error = wrapOrError(job.error, ErrInvalidMonthLastDayEntry) + return s + } } // Months sets the unit with months // Note: Only days 1 through 28 are allowed for monthly schedules -// Note: Multiple add same days of month cannot be allowed -// Note: -1 is a special value and can only occur as single argument +// Note: Multiple of the same day of month is not allowed +// Note: Negative numbers are special values and can only occur as single argument +// and count backwards from the end of the month -1 == last day of the month, -2 == penultimate day of the month func (s *Scheduler) Months(daysOfTheMonth ...int) *Scheduler { job := s.getCurrentJob() @@ -1163,7 +1221,7 @@ func (s *Scheduler) Months(daysOfTheMonth ...int) *Scheduler { job.error = wrapOrError(job.error, ErrInvalidDayOfMonthEntry) } else if len(daysOfTheMonth) == 1 { dayOfMonth := daysOfTheMonth[0] - if dayOfMonth != -1 && (dayOfMonth < 1 || dayOfMonth > 28) { + if dayOfMonth < -28 || dayOfMonth == 0 || dayOfMonth > 28 { job.error = wrapOrError(job.error, ErrInvalidDayOfMonthEntry) } } else { @@ -1255,17 +1313,19 @@ func (s *Scheduler) Sunday() *Scheduler { } func (s *Scheduler) getCurrentJob() *Job { - if !s.inScheduleChain { + if s.inScheduleChain == nil { s.jobsMutex.Lock() - s.jobs = append(s.jobs, s.newJob(0)) + j := s.newJob(0) + s.jobs[j.id] = j s.jobsMutex.Unlock() - s.inScheduleChain = true + s.inScheduleChain = &j.id + return j } s.jobsMutex.RLock() defer s.jobsMutex.RUnlock() - return s.jobs[len(s.jobs)-1] + return s.jobs[*s.inScheduleChain] } func (s *Scheduler) now() time.Time { @@ -1284,14 +1344,12 @@ func (s *Scheduler) TagsUnique() { // of making changes to the job with the scheduler chain // and finalized by calling Update() func (s *Scheduler) Job(j *Job) *Scheduler { - jobs := s.Jobs() - for index, job := range jobs { - if job == j { - // the current job is always last, so put this job there - s.Swap(len(jobs)-1, index) - } + if job, ok := s.JobsMap()[j.id]; !ok { + return s + } else if job != j { + return s } - s.inScheduleChain = true + s.inScheduleChain = &j.id s.updateJob = true return s } @@ -1425,9 +1483,6 @@ func (s *Scheduler) StopBlockingChan() { // WithDistributedLocker prevents the same job from being run more than once // when multiple schedulers are trying to schedule the same job. // -// NOTE - This is currently in BETA. Please provide any feedback on your usage -// and open bugs with any issues. -// // One strategy to reduce splay in the job execution times when using // intervals (e.g. 1s, 1m, 1h), on each scheduler instance, is to use // StartAt with time.Now().Round(interval) to start the job at the @@ -1447,13 +1502,27 @@ func (s *Scheduler) WithDistributedLocker(l Locker) { s.executor.distributedLocker = l } +// WithDistributedElector prevents the same job from being run more than once +// when multiple schedulers are trying to schedule the same job, by allowing only +// the leader to run jobs. Non-leaders wait until the leader instance goes down +// and then a new leader is elected. +// +// Compared with the distributed lock, the election is the same as leader/follower framework. +// All jobs are only scheduled and execute on the leader scheduler instance. Only when the leader scheduler goes down +// and one of the scheduler instances is successfully elected, then the new leader scheduler instance can schedule jobs. +func (s *Scheduler) WithDistributedElector(e Elector) { + s.executor.distributedElector = e +} + // RegisterEventListeners accepts EventListeners and registers them for all jobs // in the scheduler at the time this function is called. // The event listeners are then called at the times described by each listener. // If a new job is added, an additional call to this method, or the job specific // version must be executed in order for the new job to trigger event listeners. func (s *Scheduler) RegisterEventListeners(eventListeners ...EventListener) { - for _, job := range s.Jobs() { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.jobs { job.RegisterEventListeners(eventListeners...) } } diff --git a/vendor/github.com/google/uuid/CHANGELOG.md b/vendor/github.com/google/uuid/CHANGELOG.md index 2bd78667a..7ed347d3a 100644 --- a/vendor/github.com/google/uuid/CHANGELOG.md +++ b/vendor/github.com/google/uuid/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26) + + +### Features + +* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4)) + +### Fixes + +* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior) + ## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18) diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md index 556688872..a502fdc51 100644 --- a/vendor/github.com/google/uuid/CONTRIBUTING.md +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -11,7 +11,7 @@ please explain why in the pull request description. ### Releasing -Commits that would precipitate a SemVer change, as desrcibed in the Conventional +Commits that would precipitate a SemVer change, as described in the Conventional Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action) to create a release candidate pull request. Once submitted, `release-please` will create a release. diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go index a56138cc4..dc75f7d99 100644 --- a/vendor/github.com/google/uuid/uuid.go +++ b/vendor/github.com/google/uuid/uuid.go @@ -56,11 +56,15 @@ func IsInvalidLengthError(err error) bool { return ok } -// Parse decodes s into a UUID or returns an error. Both the standard UUID -// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and -// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the -// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex -// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both +// the standard UUID forms defined in RFC 4122 +// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition, +// Parse accepts non-standard strings such as the raw hex encoding +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings, +// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are +// examined in the latter case. Parse should not be used to validate strings as +// it parses non-standard encodings as indicated above. func Parse(s string) (UUID, error) { var uuid UUID switch len(s) { @@ -294,3 +298,15 @@ func DisableRandPool() { poolMu.Lock() poolPos = randPoolSize } + +// UUIDs is a slice of UUID types. +type UUIDs []UUID + +// Strings returns a string slice containing the string form of each UUID in uuids. +func (uuids UUIDs) Strings() []string { + var uuidStrs = make([]string, len(uuids)) + for i, uuid := range uuids { + uuidStrs[i] = uuid.String() + } + return uuidStrs +} diff --git a/vendor/gorm.io/gorm/License b/vendor/gorm.io/gorm/LICENSE similarity index 100% rename from vendor/gorm.io/gorm/License rename to vendor/gorm.io/gorm/LICENSE diff --git a/vendor/modules.txt b/vendor/modules.txt index 3505efe8b..77b873924 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -123,7 +123,7 @@ github.com/gabriel-vasile/mimetype github.com/gabriel-vasile/mimetype/internal/charset github.com/gabriel-vasile/mimetype/internal/json github.com/gabriel-vasile/mimetype/internal/magic -# github.com/go-co-op/gocron v1.31.2 +# github.com/go-co-op/gocron v1.36.0 ## explicit; go 1.16 github.com/go-co-op/gocron # github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 @@ -255,7 +255,7 @@ github.com/golang-queue/queue/core github.com/golang/groupcache/lru # github.com/golang/protobuf v1.5.2 ## explicit; go 1.9 -# github.com/google/uuid v1.3.1 +# github.com/google/uuid v1.4.0 ## explicit github.com/google/uuid # github.com/gorilla/websocket v1.5.0