Repository: go-co-op/gocron
Pull request #444 (2023-04-11 15:03:08)

handle case where last run would be in the future on a job update
### What does this do?


### Which issue(s) does this PR fix/relate to?
<!--- Put `Resolves #XXX` here to auto-close the issue that your PR fixes (if such) --->
#424 

### List any changes that modify/break current functionality


### Have you included tests for your changes?


### Did you document any new/modified functionality?

- [ ] Updated `example_test.go`
- [ ] Updated `README.md`

### Notes



Patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index 611fb36..e1073b7 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -15,7 +15,6 @@ issues:
 linters:
   enable:
     - bodyclose
-    - deadcode
     - errcheck
     - gofmt
     - revive
@@ -25,10 +24,8 @@ linters:
     - ineffassign
     - misspell
     - staticcheck
-    - structcheck
     - typecheck
     - unused
-    - varcheck
 
 output:
   # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
diff --git a/scheduler.go b/scheduler.go
index ffc588f..403a9be 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -198,7 +198,13 @@ func (s *Scheduler) scheduleNextRun(job *Job) (bool, nextRun) {
 
 	next := s.durationToNextRun(lastRun, job)
 
-	job.setLastRun(job.NextRun())
+	jobNextRun := job.NextRun()
+	if jobNextRun.After(now) {
+		job.setLastRun(now)
+	} else {
+		job.setLastRun(jobNextRun)
+	}
+
 	if next.dateTime.IsZero() {
 		next.dateTime = lastRun.Add(next.duration)
 		job.setNextRun(next.dateTime)


Test patch:
diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml
index c59dc43..12da885 100644
--- a/.github/workflows/go_test.yml
+++ b/.github/workflows/go_test.yml
@@ -21,7 +21,7 @@ jobs:
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3.4.0
         with:
-          version: v1.48.0
+          version: v1.51.2
       - name: Install Go
         uses: actions/setup-go@v4
         with:
diff --git a/scheduler_test.go b/scheduler_test.go
index d69cf1d..ff4ff06 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -2,7 +2,6 @@ package gocron
 
 import (
 	"fmt"
-	"log"
 	"sync"
 	"sync/atomic"
 	"testing"
@@ -255,8 +254,6 @@ func TestAt(t *testing.T) {
 
 		select {
 		case <-time.After(1 * time.Second):
-			log.Println(now.Add(time.Minute))
-			log.Println(dayJob.nextRun)
 			s.stop()
 			assert.Equal(t, now.Add(1*time.Minute), dayJob.nextRun)
 		case <-semaphore:
@@ -947,7 +944,6 @@ func TestScheduler_Stop(t *testing.T) {
 		}()
 
 		s.StartBlocking()
-		log.Println(".Stop() stops the blocking start")
 		assert.False(t, s.IsRunning())
 	})
 }
@@ -1629,11 +1625,11 @@ func TestScheduler_DoParameterValidation(t *testing.T) {
 func TestScheduler_Job(t *testing.T) {
 	s := NewScheduler(time.UTC)
 
-	j1, err := s.Every("1s").Do(func() { log.Println("one") })
+	j1, err := s.Every("1s").Do(func() {})
 	require.NoError(t, err)
 	assert.Equal(t, j1, s.getCurrentJob())
 
-	j2, err := s.Every("1s").Do(func() { log.Println("two") })
+	j2, err := s.Every("1s").Do(func() {})
 	require.NoError(t, err)
 	assert.Equal(t, j2, s.getCurrentJob())
 
@@ -1668,12 +1664,12 @@ func TestScheduler_Update(t *testing.T) {
 		_, err = s.Job(j).CronWithSeconds("*/1 * * * * *").Update()
 		require.NoError(t, err)
 
-		time.Sleep(time.Second)
+		time.Sleep(1200 * time.Millisecond)
 		s.Stop()
 
 		counterMutex.RLock()
 		defer counterMutex.RUnlock()
-		assert.Equal(t, 4, counter)
+		assert.Equal(t, 5, counter)
 	})
 
 	t.Run("happy singleton mode", func(t *testing.T) {
@@ -1682,7 +1678,11 @@ func TestScheduler_Update(t *testing.T) {
 		var counterMutex sync.RWMutex
 		counter := 0
 
-		j, err := s.Every(1).Day().SingletonMode().Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++ })
+		j, err := s.Every(1).Day().SingletonMode().Do(func() {
+			counterMutex.Lock()
+			defer counterMutex.Unlock()
+			counter++
+		})
 		require.NoError(t, err)
 
 		s.StartAsync()
@@ -1700,7 +1700,7 @@ func TestScheduler_Update(t *testing.T) {
 
 		counterMutex.RLock()
 		defer counterMutex.RUnlock()
-		assert.Equal(t, 3, counter)
+		assert.Equal(t, 4, counter)
 	})
 
 	t.Run("update called without job call", func(t *testing.T) {
@@ -1744,6 +1744,27 @@ func TestScheduler_Update(t *testing.T) {
 		defer counterMutex.RUnlock()
 		assert.Equal(t, 4, counter)
 	})
+
+	// Verifies https://github.com/go-co-op/gocron/issues/424
+	t.Run("next run calculated correctly", func(t *testing.T) {
+		s := NewScheduler(time.UTC)
+
+		job, err := s.Every(time.Second).Do(func() {})
+		require.NoError(t, err)
+		s.StartAsync()
+
+		newInterval := 5 * time.Second
+		_, err = s.Job(job).Every(newInterval).Update()
+		require.NoError(t, err)
+		s.Stop()
+
+		last := job.LastRun().Round(time.Millisecond)
+		actualNext := job.NextRun().Round(time.Millisecond)
+		expectedNext := last.Add(newInterval)
+
+		assert.Equal(t, expectedNext, actualNext)
+
+	})
 }
 
 func TestScheduler_RunByTag(t *testing.T) {
@@ -1906,10 +1927,10 @@ func TestScheduler_WaitForSchedules(t *testing.T) {
 	var counterMutex sync.RWMutex
 	counter := 0
 
-	_, err := s.Every("1s").Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++; log.Println("job 1") })
+	_, err := s.Every("1s").Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++ })
 	require.NoError(t, err)
 
-	_, err = s.CronWithSeconds("*/1 * * * * *").Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++; log.Println("job 2") })
+	_, err = s.CronWithSeconds("*/1 * * * * *").Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++ })
 	require.NoError(t, err)
 	s.StartAsync()
 
@@ -2293,9 +2314,7 @@ func TestScheduler_DoWithJobDetails(t *testing.T) {
 	t.Run("run job with details", func(t *testing.T) {
 		s := NewScheduler(time.UTC)
 
-		_, err := s.Tag("tag1").Every("100ms").DoWithJobDetails(func(job Job) {
-			log.Printf("job last run: %s, job next run: %s", job.LastRun(), job.NextRun())
-		})
+		_, err := s.Tag("tag1").Every("100ms").DoWithJobDetails(func(job Job) {})
 		require.NoError(t, err)
 		s.StartAsync()
 		time.Sleep(500 * time.Millisecond)