From 8a0422aa12520d4a286c69a1a309c32cbc03eee5 Mon Sep 17 00:00:00 2001 From: Marvin Wendt Date: Thu, 27 Jun 2024 09:35:33 +0200 Subject: [PATCH 1/2] fixed linting --- .golangci.yml | 1 + examples_test.go | 1 + schedule.go | 9 ++++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 743bfac..89a230b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -163,6 +163,7 @@ linters-settings: - d any - data any - n any + - t time.Time - f func() - cb func() - t testing.T diff --git a/examples_test.go b/examples_test.go index 1e2dba0..bc764b9 100644 --- a/examples_test.go +++ b/examples_test.go @@ -30,6 +30,7 @@ func ExampleAt() { func ExampleEvery() { task := schedule.Every(time.Second, func() bool { fmt.Println("1 second is over!") + return true // return false to stop the task }) diff --git a/schedule.go b/schedule.go index 635d29d..b332691 100644 --- a/schedule.go +++ b/schedule.go @@ -55,13 +55,13 @@ func (s *Task) Stop() { // After executes the task after the given duration. // The function is non-blocking. If you want to wait for the task to be executed, use the Task.Wait method. -func After(d time.Duration, task func()) *Task { +func After(duration time.Duration, task func()) *Task { scheduler := newTask() - scheduler.nextExecution = time.Now().Add(d) + scheduler.nextExecution = time.Now().Add(duration) go func() { select { - case <-time.After(d): + case <-time.After(duration): task() scheduler.Stop() case <-scheduler.stop: @@ -104,9 +104,12 @@ func Every(interval time.Duration, task func() bool) *Task { select { case <-ticker.C: task() + scheduler.nextExecution = time.Now().Add(interval) + case <-scheduler.stop: ticker.Stop() + return } } From 501ea1d758f14b2de566850a743aedc021ff2a31 Mon Sep 17 00:00:00 2001 From: Marvin Wendt Date: Thu, 27 Feb 2025 13:18:36 +0100 Subject: [PATCH 2/2] perf: increase performance and fix potential concurrency issues --- schedule.go | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/schedule.go b/schedule.go index b12c18a..331c69b 100644 --- a/schedule.go +++ b/schedule.go @@ -1,13 +1,18 @@ package schedule -import "time" +import ( + "sync" + "sync/atomic" + "time" +) // Task holds information about the running task and can be used to stop running tasks. type Task struct { stop chan struct{} nextExecution time.Time startedAt time.Time - stopped bool + stopped int32 // 0 means active, 1 means stopped + once sync.Once } // newTask creates a new Task. @@ -35,7 +40,7 @@ func (s *Task) ExecutesIn() time.Duration { // IsActive returns true if the scheduler is active. func (s *Task) IsActive() bool { - return !s.stopped + return atomic.LoadInt32(&s.stopped) == 0 } // Wait blocks until the scheduler is stopped. @@ -46,12 +51,10 @@ func (s *Task) Wait() { // Stop stops the scheduler. func (s *Task) Stop() { - if s.stopped { - return - } - - s.stopped = true - close(s.stop) + s.once.Do(func() { + atomic.StoreInt32(&s.stopped, 1) + close(s.stop) + }) } // After executes the task after the given duration. @@ -59,13 +62,18 @@ func (s *Task) Stop() { func After(duration time.Duration, task func()) *Task { scheduler := newTask() scheduler.nextExecution = time.Now().Add(duration) + timer := time.NewTimer(duration) go func() { select { - case <-time.After(duration): + case <-timer.C: task() scheduler.Stop() case <-scheduler.stop: + // If the task is stopped before the timer fires, stop the timer. + if !timer.Stop() { + <-timer.C // drain if necessary + } return } }() @@ -78,13 +86,21 @@ func After(duration time.Duration, task func()) *Task { func At(t time.Time, task func()) *Task { scheduler := newTask() scheduler.nextExecution = t + d := time.Until(t) + if d < 0 { + d = 0 + } + timer := time.NewTimer(d) go func() { select { - case <-time.After(time.Until(t)): + case <-timer.C: task() scheduler.Stop() case <-scheduler.stop: + if !timer.Stop() { + <-timer.C + } return } }() @@ -97,23 +113,20 @@ func At(t time.Time, task func()) *Task { func Every(interval time.Duration, task func() bool) *Task { scheduler := newTask() scheduler.nextExecution = time.Now().Add(interval) - ticker := time.NewTicker(interval) go func() { for { select { case <-ticker.C: - res := task() - if !res { + if !task() { scheduler.Stop() + ticker.Stop() + return } - scheduler.nextExecution = time.Now().Add(interval) - case <-scheduler.stop: ticker.Stop() - return } }