diff --git a/README.md b/README.md index 54de441..5191f65 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,6 @@ gocron is a job scheduling package which lets you run Go functions at pre-determ If you want to chat, you can find us on Slack at [](https://gophers.slack.com/archives/CQ7T0T1FW) -## Concepts - -- **Job**: The encapsulates a "task", which is made up of a go func and any function parameters, and then - provides the scheduler with the time the job should be scheduled to run. -- **Executor**: The executor, calls the "task" function and manages the complexities of different job - execution timing (e.g. singletons that shouldn't overrun each other, limiting the max number of jobs running) -- **Scheduler**: The scheduler keeps track of all the jobs and sends each job to the executor when - it is ready to be run. - ## Quick Start ``` @@ -70,6 +61,16 @@ func main() { } ``` +## 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. @@ -85,7 +86,7 @@ func main() { Jobs can be run every x weeks on specific days of the week and at specific times. - [**Monthly**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#MonthlyJob): Jobs can be run every x months on specific days of the month and at specific times. -- **Limited Concurrency**: Jobs can be limited individually or across the entire scheduler. +- **Concurrency Limits**: Jobs can be limited individually or across the entire scheduler. - [**Per job limiting with singleton mode**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithSingletonMode): Jobs can be limited to a single concurrent execution that either reschedules (skips overlapping executions) or queues (waits for the previous execution to finish). @@ -93,6 +94,7 @@ func main() { 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**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithDistributedElector): An elector can be used to elect a single instance of gocron to run as the primary with the @@ -103,31 +105,33 @@ func main() { - Implementations: [go-co-op lockers](https://github.com/go-co-op?q=-lock&type=all&language=&sort=) - **Events**: Job events can trigger actions. - [**Listeners**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithEventListeners): - [Event listeners](https://pkg.go.dev/github.com/go-co-op/gocron/v2#EventListener) - can be added to a job or all jobs in the + Can be added to a job, with [event listeners](https://pkg.go.dev/github.com/go-co-op/gocron/v2#EventListener), + or all jobs across the [scheduler](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithGlobalJobOptions) to listen for job events and trigger actions. -- **Options**: Many job and scheduler options are available +- **Options**: Many job and scheduler options are available. - [**Job options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#JobOption): Job options can be set when creating a job using `NewJob`. - [**Global job options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithGlobalJobOptions): - Global job options can be set when creating a scheduler using `NewScheduler`. + Global job options can be set when creating a scheduler using `NewScheduler` + and the `WithGlobalJobOptions` option. - [**Scheduler options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#SchedulerOption): Scheduler options can be set when creating a scheduler using `NewScheduler`. - **Logging**: Logs can be enabled. - [Logger](https://pkg.go.dev/github.com/go-co-op/gocron/v2#Logger): The Logger interface can be implemented with your desired logging library. The provided NewLogger uses the standard library's log package. -- **Mocking**: The gocron library is set up to enable testing. +- **Testing**: The gocron library is set up to enable testing. - Mocks are provided in [the mock package](mocks) using [gomock](https://github.com/uber-go/mock). - Time can be mocked by passing in a [FakeClock](https://pkg.go.dev/github.com/jonboulle/clockwork#FakeClock) to [WithClock](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithClock) - - see the example on WithClock in the go-docs. + see the [example on WithClock](https://pkg.go.dev/github.com/go-co-op/gocron/v2#example-WithClock). ## Supporters -[Jetbrains](https://www.jetbrains.com/?from=gocron) supports this project with Intellij licenses. -We appreciate their support for free and open source software! +We appreciate the support for free and open source software! + +- [Jetbrains](https://www.jetbrains.com/?from=gocron) supports this project with Intellij licenses. ## Star History diff --git a/distributed.go b/distributed.go index 60d1b84..1617c62 100644 --- a/distributed.go +++ b/distributed.go @@ -1,4 +1,4 @@ -//go:generate mockgen -source=distributed.go -destination=mocks/distributed.go -package=gocronmocks +//go:generate mockgen -destination=mocks/distributed.go -package=gocronmocks . Elector,Locker,Lock package gocron import ( diff --git a/executor.go b/executor.go index b562a28..d13d5aa 100644 --- a/executor.go +++ b/executor.go @@ -231,6 +231,13 @@ func (e *executor) limitModeRunner(name string, in chan uuid.UUID, wg *waitGroup return case e.jobIDsOut <- j.id: } + // remove the limiter block to allow another job to be scheduled + if limitMode == LimitModeReschedule { + select { + case <-rescheduleLimiter: + default: + } + } continue } e.limitMode.singletonJobs[id] = struct{}{} diff --git a/go.mod b/go.mod index e365b58..aaa6ee4 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,13 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.8.4 go.uber.org/goleak v1.3.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 529bbf4..125575e 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,12 @@ 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/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -19,10 +19,10 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/job.go b/job.go index 020f60e..6345ad3 100644 --- a/job.go +++ b/job.go @@ -1,4 +1,4 @@ -//go:generate mockgen -source=job.go -destination=mocks/job.go -package=gocronmocks +//go:generate mockgen -destination=mocks/job.go -package=gocronmocks . Job package gocron import ( diff --git a/job_test.go b/job_test.go index 73beccd..16d8127 100644 --- a/job_test.go +++ b/job_test.go @@ -313,10 +313,9 @@ func TestJob_LastRun(t *testing.T) { testTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local) fakeClock := clockwork.NewFakeClockAt(testTime) - s, err := newTestScheduler( + s := newTestScheduler(t, WithClock(fakeClock), ) - require.NoError(t, err) j, err := s.NewJob( DurationJob( diff --git a/logger.go b/logger.go index 51b2ce7..c8ebaf8 100644 --- a/logger.go +++ b/logger.go @@ -1,4 +1,4 @@ -//go:generate mockgen -source=logger.go -destination=mocks/logger.go -package=gocronmocks +//go:generate mockgen -destination=mocks/logger.go -package=gocronmocks . Logger package gocron import ( diff --git a/mocks/distributed.go b/mocks/distributed.go index 51131af..357dc2b 100644 --- a/mocks/distributed.go +++ b/mocks/distributed.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: distributed.go +// Source: github.com/go-co-op/gocron/v2 (interfaces: Elector,Locker,Lock) // // Generated by this command: // -// mockgen -source=distributed.go -destination=mocks/distributed.go -package=gocronmocks +// mockgen -destination=mocks/distributed.go -package=gocronmocks . Elector,Locker,Lock // // Package gocronmocks is a generated GoMock package. package gocronmocks @@ -12,6 +12,7 @@ import ( context "context" reflect "reflect" + gocron "github.com/go-co-op/gocron/v2" gomock "go.uber.org/mock/gomock" ) @@ -51,3 +52,78 @@ func (mr *MockElectorMockRecorder) IsLeader(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLeader", reflect.TypeOf((*MockElector)(nil).IsLeader), arg0) } + +// MockLocker is a mock of Locker interface. +type MockLocker struct { + ctrl *gomock.Controller + recorder *MockLockerMockRecorder +} + +// MockLockerMockRecorder is the mock recorder for MockLocker. +type MockLockerMockRecorder struct { + mock *MockLocker +} + +// NewMockLocker creates a new mock instance. +func NewMockLocker(ctrl *gomock.Controller) *MockLocker { + mock := &MockLocker{ctrl: ctrl} + mock.recorder = &MockLockerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLocker) EXPECT() *MockLockerMockRecorder { + return m.recorder +} + +// Lock mocks base method. +func (m *MockLocker) Lock(arg0 context.Context, arg1 string) (gocron.Lock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Lock", arg0, arg1) + ret0, _ := ret[0].(gocron.Lock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Lock indicates an expected call of Lock. +func (mr *MockLockerMockRecorder) Lock(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockLocker)(nil).Lock), arg0, arg1) +} + +// MockLock is a mock of Lock interface. +type MockLock struct { + ctrl *gomock.Controller + recorder *MockLockMockRecorder +} + +// MockLockMockRecorder is the mock recorder for MockLock. +type MockLockMockRecorder struct { + mock *MockLock +} + +// NewMockLock creates a new mock instance. +func NewMockLock(ctrl *gomock.Controller) *MockLock { + mock := &MockLock{ctrl: ctrl} + mock.recorder = &MockLockMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLock) EXPECT() *MockLockMockRecorder { + return m.recorder +} + +// Unlock mocks base method. +func (m *MockLock) Unlock(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Unlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockLockMockRecorder) Unlock(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockLock)(nil).Unlock), arg0) +} diff --git a/mocks/go.mod b/mocks/go.mod index 855f473..3870e76 100644 --- a/mocks/go.mod +++ b/mocks/go.mod @@ -3,7 +3,7 @@ module github.com/go-co-op/gocronmocks/v2 go 1.20 require ( - github.com/go-co-op/gocron/v2 v2.0.0-rc1 + github.com/go-co-op/gocron/v2 v2.0.0-rc4 github.com/google/uuid v1.4.0 go.uber.org/mock v0.3.0 ) @@ -11,5 +11,5 @@ require ( require ( github.com/jonboulle/clockwork v0.4.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect ) diff --git a/mocks/go.sum b/mocks/go.sum index 10fd076..472c71d 100644 --- a/mocks/go.sum +++ b/mocks/go.sum @@ -1,6 +1,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/go-co-op/gocron/v2 v2.0.0-rc1 h1:qkj0WVO6uh6ibZa2CQ0Ifxk3A+c6rawZnIby94/5sAM= -github.com/go-co-op/gocron/v2 v2.0.0-rc1/go.mod h1:3SLoqKnyORFVN0VvFFb1383hM4WD9XHBPn9aUUp7sQs= +github.com/go-co-op/gocron/v2 v2.0.0-rc4 h1:KFYg2CzyHZwPZL/uNnQKEyeL9oKEUQbiLThArcZaVmw= +github.com/go-co-op/gocron/v2 v2.0.0-rc4/go.mod h1:3SLoqKnyORFVN0VvFFb1383hM4WD9XHBPn9aUUp7sQs= 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/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= @@ -12,6 +12,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/mocks/job.go b/mocks/job.go index 61b06ad..0f17772 100644 --- a/mocks/job.go +++ b/mocks/job.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: job.go +// Source: github.com/go-co-op/gocron/v2 (interfaces: Job) // // Generated by this command: // -// mockgen -source=job.go -destination=mocks/job.go -package=gocronmocks +// mockgen -destination=mocks/job.go -package=gocronmocks . Job // // Package gocronmocks is a generated GoMock package. package gocronmocks @@ -16,80 +16,6 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockJobDefinition is a mock of JobDefinition interface. -type MockJobDefinition struct { - ctrl *gomock.Controller - recorder *MockJobDefinitionMockRecorder -} - -// MockJobDefinitionMockRecorder is the mock recorder for MockJobDefinition. -type MockJobDefinitionMockRecorder struct { - mock *MockJobDefinition -} - -// NewMockJobDefinition creates a new mock instance. -func NewMockJobDefinition(ctrl *gomock.Controller) *MockJobDefinition { - mock := &MockJobDefinition{ctrl: ctrl} - mock.recorder = &MockJobDefinitionMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockJobDefinition) EXPECT() *MockJobDefinitionMockRecorder { - return m.recorder -} - -// setup mocks base method. -func (m *MockJobDefinition) setup(arg0 *internalJob, arg1 *time.Location) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "setup", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// setup indicates an expected call of setup. -func (mr *MockJobDefinitionMockRecorder) setup(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setup", reflect.TypeOf((*MockJobDefinition)(nil).setup), arg0, arg1) -} - -// MockjobSchedule is a mock of jobSchedule interface. -type MockjobSchedule struct { - ctrl *gomock.Controller - recorder *MockjobScheduleMockRecorder -} - -// MockjobScheduleMockRecorder is the mock recorder for MockjobSchedule. -type MockjobScheduleMockRecorder struct { - mock *MockjobSchedule -} - -// NewMockjobSchedule creates a new mock instance. -func NewMockjobSchedule(ctrl *gomock.Controller) *MockjobSchedule { - mock := &MockjobSchedule{ctrl: ctrl} - mock.recorder = &MockjobScheduleMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockjobSchedule) EXPECT() *MockjobScheduleMockRecorder { - return m.recorder -} - -// next mocks base method. -func (m *MockjobSchedule) next(lastRun time.Time) time.Time { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "next", lastRun) - ret0, _ := ret[0].(time.Time) - return ret0 -} - -// next indicates an expected call of next. -func (mr *MockjobScheduleMockRecorder) next(lastRun any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "next", reflect.TypeOf((*MockjobSchedule)(nil).next), lastRun) -} - // MockJob is a mock of Job interface. type MockJob struct { ctrl *gomock.Controller diff --git a/mocks/logger.go b/mocks/logger.go index 5b4789a..abc1b7a 100644 --- a/mocks/logger.go +++ b/mocks/logger.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: logger.go +// Source: github.com/go-co-op/gocron/v2 (interfaces: Logger) // // Generated by this command: // -// mockgen -source=logger.go -destination=mocks/logger.go -package=gocronmocks +// mockgen -destination=mocks/logger.go -package=gocronmocks . Logger // // Package gocronmocks is a generated GoMock package. package gocronmocks @@ -38,69 +38,69 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { } // Debug mocks base method. -func (m *MockLogger) Debug(msg string, args ...any) { +func (m *MockLogger) Debug(arg0 string, arg1 ...any) { m.ctrl.T.Helper() - varargs := []any{msg} - for _, a := range args { + varargs := []any{arg0} + for _, a := range arg1 { varargs = append(varargs, a) } m.ctrl.Call(m, "Debug", varargs...) } // Debug indicates an expected call of Debug. -func (mr *MockLoggerMockRecorder) Debug(msg any, args ...any) *gomock.Call { +func (mr *MockLoggerMockRecorder) Debug(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{msg}, args...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), varargs...) } // Error mocks base method. -func (m *MockLogger) Error(msg string, args ...any) { +func (m *MockLogger) Error(arg0 string, arg1 ...any) { m.ctrl.T.Helper() - varargs := []any{msg} - for _, a := range args { + varargs := []any{arg0} + for _, a := range arg1 { varargs = append(varargs, a) } m.ctrl.Call(m, "Error", varargs...) } // Error indicates an expected call of Error. -func (mr *MockLoggerMockRecorder) Error(msg any, args ...any) *gomock.Call { +func (mr *MockLoggerMockRecorder) Error(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{msg}, args...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) } // Info mocks base method. -func (m *MockLogger) Info(msg string, args ...any) { +func (m *MockLogger) Info(arg0 string, arg1 ...any) { m.ctrl.T.Helper() - varargs := []any{msg} - for _, a := range args { + varargs := []any{arg0} + for _, a := range arg1 { varargs = append(varargs, a) } m.ctrl.Call(m, "Info", varargs...) } // Info indicates an expected call of Info. -func (mr *MockLoggerMockRecorder) Info(msg any, args ...any) *gomock.Call { +func (mr *MockLoggerMockRecorder) Info(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{msg}, args...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) } // Warn mocks base method. -func (m *MockLogger) Warn(msg string, args ...any) { +func (m *MockLogger) Warn(arg0 string, arg1 ...any) { m.ctrl.T.Helper() - varargs := []any{msg} - for _, a := range args { + varargs := []any{arg0} + for _, a := range arg1 { varargs = append(varargs, a) } m.ctrl.Call(m, "Warn", varargs...) } // Warn indicates an expected call of Warn. -func (mr *MockLoggerMockRecorder) Warn(msg any, args ...any) *gomock.Call { +func (mr *MockLoggerMockRecorder) Warn(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{msg}, args...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), varargs...) } diff --git a/mocks/scheduler.go b/mocks/scheduler.go index ff01aed..d4180c8 100644 --- a/mocks/scheduler.go +++ b/mocks/scheduler.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: scheduler.go +// Source: github.com/go-co-op/gocron/v2 (interfaces: Scheduler) // // Generated by this command: // -// mockgen -source=scheduler.go -destination=mocks/scheduler.go -package=gocronmocks +// mockgen -destination=mocks/scheduler.go -package=gocronmocks . Scheduler // // Package gocronmocks is a generated GoMock package. package gocronmocks @@ -11,7 +11,7 @@ package gocronmocks import ( reflect "reflect" - v2 "github.com/go-co-op/gocron/v2" + gocron "github.com/go-co-op/gocron/v2" uuid "github.com/google/uuid" gomock "go.uber.org/mock/gomock" ) @@ -40,10 +40,10 @@ func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder { } // Jobs mocks base method. -func (m *MockScheduler) Jobs() []v2.Job { +func (m *MockScheduler) Jobs() []gocron.Job { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Jobs") - ret0, _ := ret[0].([]v2.Job) + ret0, _ := ret[0].([]gocron.Job) return ret0 } @@ -54,14 +54,14 @@ func (mr *MockSchedulerMockRecorder) Jobs() *gomock.Call { } // NewJob mocks base method. -func (m *MockScheduler) NewJob(arg0 v2.JobDefinition, arg1 v2.Task, arg2 ...v2.JobOption) (v2.Job, error) { +func (m *MockScheduler) NewJob(arg0 gocron.JobDefinition, arg1 gocron.Task, arg2 ...gocron.JobOption) (gocron.Job, error) { m.ctrl.T.Helper() varargs := []any{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "NewJob", varargs...) - ret0, _ := ret[0].(v2.Job) + ret0, _ := ret[0].(gocron.Job) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -144,14 +144,14 @@ func (mr *MockSchedulerMockRecorder) StopJobs() *gomock.Call { } // Update mocks base method. -func (m *MockScheduler) Update(arg0 uuid.UUID, arg1 v2.JobDefinition, arg2 v2.Task, arg3 ...v2.JobOption) (v2.Job, error) { +func (m *MockScheduler) Update(arg0 uuid.UUID, arg1 gocron.JobDefinition, arg2 gocron.Task, arg3 ...gocron.JobOption) (gocron.Job, error) { m.ctrl.T.Helper() varargs := []any{arg0, arg1, arg2} for _, a := range arg3 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "Update", varargs...) - ret0, _ := ret[0].(v2.Job) + ret0, _ := ret[0].(gocron.Job) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/scheduler.go b/scheduler.go index a9946f8..6101e58 100644 --- a/scheduler.go +++ b/scheduler.go @@ -1,4 +1,4 @@ -//go:generate mockgen -source=scheduler.go -destination=mocks/scheduler.go -package=gocronmocks +//go:generate mockgen -destination=mocks/scheduler.go -package=gocronmocks . Scheduler package gocron import ( diff --git a/scheduler_test.go b/scheduler_test.go index 9b8231e..eab8ac4 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -14,7 +14,7 @@ import ( "go.uber.org/goleak" ) -func newTestScheduler(options ...SchedulerOption) (Scheduler, error) { +func newTestScheduler(t *testing.T, options ...SchedulerOption) Scheduler { // default test options out := []SchedulerOption{ WithLogger(NewLogger(LogLevelDebug)), @@ -23,7 +23,9 @@ func newTestScheduler(options ...SchedulerOption) (Scheduler, error) { // append any additional options 2nd to override defaults if needed out = append(out, options...) - return NewScheduler(out...) + s, err := NewScheduler(out...) + require.NoError(t, err) + return s } func TestScheduler_OneSecond_NoOptions(t *testing.T) { @@ -66,10 +68,9 @@ func TestScheduler_OneSecond_NoOptions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) + s := newTestScheduler(t) - _, err = s.NewJob(tt.jd, tt.tsk) + _, err := s.NewJob(tt.jd, tt.tsk) require.NoError(t, err) s.Start() @@ -147,10 +148,9 @@ func TestScheduler_LongRunningJobs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler(tt.options...) - require.NoError(t, err) + s := newTestScheduler(t, tt.options...) - _, err = s.NewJob(tt.jd, tt.tsk, tt.opts...) + _, err := s.NewJob(tt.jd, tt.tsk, tt.opts...) require.NoError(t, err) s.Start() @@ -217,8 +217,7 @@ func TestScheduler_Update(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) + s := newTestScheduler(t) j, err := s.NewJob(tt.initialJob, tt.tsk) require.NoError(t, err) @@ -294,12 +293,11 @@ func TestScheduler_StopTimeout(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testDoneCtx, cancel := context.WithCancel(context.Background()) - s, err := newTestScheduler( - WithStopTimeout(time.Millisecond * 100), + s := newTestScheduler(t, + WithStopTimeout(time.Millisecond*100), ) - require.NoError(t, err) - _, err = s.NewJob(tt.jd, NewTask(tt.f, testDoneCtx), tt.opts...) + _, err := s.NewJob(tt.jd, NewTask(tt.f, testDoneCtx), tt.opts...) require.NoError(t, err) s.Start() @@ -316,11 +314,11 @@ func TestScheduler_Shutdown(t *testing.T) { goleak.VerifyNone(t) t.Run("start, stop, start, shutdown", func(t *testing.T) { - s, err := newTestScheduler( + s := newTestScheduler(t, WithStopTimeout(time.Second), ) - require.NoError(t, err) - _, err = s.NewJob( + + _, err := s.NewJob( DurationJob( 50*time.Millisecond, ), @@ -346,10 +344,9 @@ func TestScheduler_Shutdown(t *testing.T) { }) t.Run("calling Job methods after shutdown errors", func(t *testing.T) { - s, err := newTestScheduler( + s := newTestScheduler(t, WithStopTimeout(time.Second), ) - require.NoError(t, err) j, err := s.NewJob( DurationJob( 100*time.Millisecond, @@ -461,10 +458,9 @@ func TestScheduler_NewJob(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) + s := newTestScheduler(t) - _, err = s.NewJob(tt.jd, tt.tsk, tt.opts...) + _, err := s.NewJob(tt.jd, tt.tsk, tt.opts...) require.NoError(t, err) s.Start() @@ -727,23 +723,21 @@ func TestScheduler_NewJobErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler( - WithStopTimeout(time.Millisecond * 50), + s := newTestScheduler(t, + WithStopTimeout(time.Millisecond*50), ) - require.NoError(t, err) - _, err = s.NewJob(tt.jd, NewTask(func() {}), tt.opts...) + _, err := s.NewJob(tt.jd, NewTask(func() {}), tt.opts...) assert.ErrorIs(t, err, tt.err) require.NoError(t, s.Shutdown()) }) t.Run(tt.name+" global", func(t *testing.T) { - s, err := newTestScheduler( + s := newTestScheduler(t, WithStopTimeout(time.Millisecond*50), WithGlobalJobOptions(tt.opts...), ) - require.NoError(t, err) - _, err = s.NewJob(tt.jd, NewTask(func() {})) + _, err := s.NewJob(tt.jd, NewTask(func() {})) assert.ErrorIs(t, err, tt.err) require.NoError(t, s.Shutdown()) }) @@ -784,10 +778,9 @@ func TestScheduler_NewJobTask(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) + s := newTestScheduler(t) - _, err = s.NewJob(DurationJob(time.Second), tt.tsk) + _, err := s.NewJob(DurationJob(time.Second), tt.tsk) assert.ErrorIs(t, err, tt.err) require.NoError(t, s.Shutdown()) }) @@ -845,7 +838,7 @@ func TestScheduler_WithOptionsErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := newTestScheduler(tt.opt) + _, err := NewScheduler(tt.opt) assert.ErrorIs(t, err, tt.err) }) } @@ -875,13 +868,12 @@ func TestScheduler_Singleton(t *testing.T) { t.Run(tt.name, func(t *testing.T) { jobRanCh := make(chan struct{}, 10) - s, err := newTestScheduler( + s := newTestScheduler(t, WithStopTimeout(1*time.Second), WithLocation(time.Local), ) - require.NoError(t, err) - _, err = s.NewJob( + _, err := s.NewJob( DurationJob( tt.duration, ), @@ -948,16 +940,15 @@ func TestScheduler_LimitMode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler( + s := newTestScheduler(t, WithLimitConcurrentJobs(tt.limit, tt.limitMode), WithStopTimeout(2*time.Second), ) - require.NoError(t, err) jobRanCh := make(chan struct{}, 20) for i := 0; i < tt.numJobs; i++ { - _, err = s.NewJob( + _, err := s.NewJob( DurationJob(tt.duration), NewTask(func() { time.Sleep(tt.duration / 2) @@ -1024,17 +1015,16 @@ func TestScheduler_LimitModeAndSingleton(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler( + s := newTestScheduler(t, WithLimitConcurrentJobs(tt.limit, tt.limitMode), WithStopTimeout(2*time.Second), ) - require.NoError(t, err) jobRanCh := make(chan int, 20) for i := 0; i < tt.numJobs; i++ { jobNum := i - _, err = s.NewJob( + _, err := s.NewJob( DurationJob(tt.duration), NewTask(func() { time.Sleep(tt.duration / 2) @@ -1149,15 +1139,14 @@ func TestScheduler_WithDistributed(t *testing.T) { schedulersDone := make(chan struct{}, tt.count) for i := tt.count; i > 0; i-- { - s, err := newTestScheduler( + s := newTestScheduler(t, tt.opt, ) - require.NoError(t, err) go func() { s.Start() - _, err = s.NewJob( + _, err := s.NewJob( DurationJob( time.Second, ), @@ -1229,8 +1218,7 @@ func TestScheduler_RemoveJob(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) + s := newTestScheduler(t) var id uuid.UUID if tt.addJob { @@ -1242,7 +1230,7 @@ func TestScheduler_RemoveJob(t *testing.T) { } time.Sleep(50 * time.Millisecond) - err = s.RemoveJob(id) + err := s.RemoveJob(id) assert.ErrorIs(t, err, err) require.NoError(t, s.Shutdown()) }) @@ -1310,9 +1298,8 @@ func TestScheduler_WithEventListeners(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, err := newTestScheduler() - require.NoError(t, err) - _, err = s.NewJob( + s := newTestScheduler(t) + _, err := s.NewJob( DurationJob(time.Minute*10), tt.tsk, WithStartAt( @@ -1343,3 +1330,36 @@ func TestScheduler_WithEventListeners(t *testing.T) { }) } } + +func TestScheduler_ManyJobs(t *testing.T) { + s := newTestScheduler(t) + jobsRan := make(chan struct{}, 20000) + + for i := 1; i <= 1000; i++ { + _, err := s.NewJob( + DurationJob( + time.Millisecond*100, + ), + NewTask( + func() { + jobsRan <- struct{}{} + }, + ), + WithStartAt(WithStartImmediately()), + ) + require.NoError(t, err) + } + + s.Start() + time.Sleep(1 * time.Second) + require.NoError(t, s.Shutdown()) + close(jobsRan) + + var count int + for range jobsRan { + count++ + } + + assert.GreaterOrEqual(t, count, 9900) + assert.LessOrEqual(t, count, 11000) +}