diff --git a/README.md b/README.md index 26a9256c..98105692 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ func main() { s2.Every(1).Week().SetTag(tag2).Do(task) // Removing Job Based on Tag - scheduler.RemoveJobByTag("tag1") + s2.RemoveJobByTag("tag1") // Do jobs on specific weekday s2.Every(1).Monday().Do(task) @@ -98,6 +98,9 @@ func main() { // Clear all scheduled jobs s2.Clear() + // stop our first scheduler (it still exists but doesn't run anymore) + s1.Stop() + // executes the scheduler and blocks current thread s2.StartBlocking() diff --git a/job.go b/job.go index 8d1f4e9a..682b077b 100644 --- a/job.go +++ b/job.go @@ -25,7 +25,7 @@ type Job struct { // NewJob creates a new Job with the provided interval func NewJob(interval uint64) *Job { - th := newTimeHelper() + th := newTimeWrapper() return &Job{ interval: interval, lastRun: th.Unix(0, 0), diff --git a/scheduler.go b/scheduler.go index da30db34..bfbcfcf8 100644 --- a/scheduler.go +++ b/scheduler.go @@ -13,15 +13,21 @@ import ( type Scheduler struct { jobs []*Job loc *time.Location - time timeHelper // an instance of timeHelper to interact with the time package + + running bool + stopChan chan struct{} // signal to stop scheduling + + time timeWrapper // wrapper around time.Time } // NewScheduler creates a new Scheduler func NewScheduler(loc *time.Location) *Scheduler { return &Scheduler{ - jobs: newEmptyJobSlice(), - loc: loc, - time: newTimeHelper(), + jobs: make([]*Job, 0), + loc: loc, + running: false, + stopChan: make(chan struct{}), + time: newTimeWrapper(), } } @@ -32,22 +38,26 @@ func (s *Scheduler) StartBlocking() { // StartAsync starts a goroutine that runs all the pending using a second-long ticker func (s *Scheduler) StartAsync() chan struct{} { - stopped := make(chan struct{}) - ticker := s.time.NewTicker(1 * time.Second) + if s.running { + return s.stopChan + } + s.running = true + ticker := s.time.NewTicker(1 * time.Second) go func() { for { select { case <-ticker.C: s.RunPending() - case <-stopped: + case <-s.stopChan: ticker.Stop() + s.running = false return } } }() - return stopped + return s.stopChan } // Jobs returns the list of Jobs from the Scheduler @@ -249,14 +259,20 @@ func (s *Scheduler) Scheduled(j interface{}) bool { return false } -// Clear delete all scheduled Jobs +// Clear clear all Jobs from this scheduler func (s *Scheduler) Clear() { - s.jobs = newEmptyJobSlice() + s.jobs = make([]*Job, 0) +} + +// Stop stops the scheduler. This is a no-op if the scheduler is already stopped . +func (s *Scheduler) Stop() { + if s.running { + s.stopScheduler() + } } -func newEmptyJobSlice() []*Job { - const initialCapacity = 256 - return make([]*Job, 0, initialCapacity) +func (s *Scheduler) stopScheduler() { + s.stopChan <- struct{}{} } // Do specifies the jobFunc that should be called every time the Job runs diff --git a/scheduler_test.go b/scheduler_test.go index 1adce944..7b717904 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -518,5 +518,18 @@ func TestSetUnit(t *testing.T) { assert.Equal(t, tc.timeUnit, j.unit) }) } +} +func TestScheduler_Stop(t *testing.T) { + t.Run("stops a running scheduler", func(t *testing.T) { + sched := NewScheduler(time.UTC) + sched.StartAsync() + sched.Stop() + assert.False(t, sched.running) + }) + t.Run("noop on stopped scheduler", func(t *testing.T) { + sched := NewScheduler(time.UTC) + sched.Stop() + assert.False(t, sched.running) + }) } diff --git a/timeHelper.go b/timeHelper.go index 02ff240d..50665ce4 100644 --- a/timeHelper.go +++ b/timeHelper.go @@ -2,7 +2,7 @@ package gocron import "time" -type timeHelper interface { +type timeWrapper interface { Now(*time.Location) time.Time Unix(int64, int64) time.Time Sleep(time.Duration) @@ -10,7 +10,7 @@ type timeHelper interface { NewTicker(time.Duration) *time.Ticker } -func newTimeHelper() timeHelper { +func newTimeWrapper() timeWrapper { return &trueTime{} }