From 5b2fe32639c3d806ce8659ed8e14bba5cd801a51 Mon Sep 17 00:00:00 2001 From: John Roesler Date: Mon, 24 Nov 2025 10:08:17 -0600 Subject: [PATCH 1/4] fix: WithStartDateTimePast now correctly calculates from past time (#897) --- job.go | 6 ++---- scheduler.go | 11 +++++++++++ scheduler_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/job.go b/job.go index 8bdb3c6..4a5295c 100644 --- a/job.go +++ b/job.go @@ -1022,16 +1022,14 @@ type weeklyJob struct { } func (w weeklyJob) next(lastRun time.Time) time.Time { - firstPass := true - next := w.nextWeekDayAtTime(lastRun, firstPass) + next := w.nextWeekDayAtTime(lastRun, true) if !next.IsZero() { return next } - firstPass = false startOfTheNextIntervalWeek := (lastRun.Day() - int(lastRun.Weekday())) + int(w.interval*7) from := time.Date(lastRun.Year(), lastRun.Month(), startOfTheNextIntervalWeek, 0, 0, 0, 0, lastRun.Location()) - return w.nextWeekDayAtTime(from, firstPass) + return w.nextWeekDayAtTime(from, false) } func (w weeklyJob) nextWeekDayAtTime(lastRun time.Time, firstPass bool) time.Time { diff --git a/scheduler.go b/scheduler.go index 274f1e2..382c55f 100644 --- a/scheduler.go +++ b/scheduler.go @@ -508,6 +508,12 @@ func (s *scheduler) selectNewJob(in newJobIn) { next = j.next(s.now()) } + if next.Before(s.now()) { + for next.Before(s.now()) { + next = j.next(next) + } + } + id := j.id j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() { select { @@ -559,6 +565,11 @@ func (s *scheduler) selectStart() { if next.IsZero() { next = j.next(s.now()) } + if next.Before(s.now()) { + for next.Before(s.now()) { + next = j.next(next) + } + } jobID := id j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() { diff --git a/scheduler_test.go b/scheduler_test.go index 1ae7431..e040576 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -2876,3 +2876,34 @@ func TestScheduler_WithMonitor(t *testing.T) { }) } } + +func TestScheduler_WithStartAtDateTimePast(t *testing.T) { + defer verifyNoGoroutineLeaks(t) + + // Monday + testTime := time.Date(2024, time.January, 1, 9, 0, 0, 0, time.UTC) + + fakeClock := clockwork.NewFakeClockAt(testTime) + + s := newTestScheduler(t, WithClock(fakeClock)) + j, err := s.NewJob( + WeeklyJob(2, NewWeekdays(time.Sunday), NewAtTimes(NewAtTime(10, 0, 0))), + NewTask(func() {}), + WithStartAt( + // The start time is in the past (Dec 30, 2023 9am) which is a Saturday + WithStartDateTimePast(testTime.Add(-time.Hour*24*2)), + ), + ) + require.NoError(t, err) + + s.Start() + + nextRun, err := j.NextRun() + require.NoError(t, err) + + require.NoError(t, s.Shutdown()) + + // Because the start time was in the past - we expect it to schedule 2 intervals ahead, pasing the first available Sunday + // which was in the past Dec 31, 2023, so the next is Jan 7, 2024 + assert.Equal(t, time.Date(2024, time.January, 7, 10, 0, 0, 0, time.UTC), nextRun) +} From 40e703737ffd6032a9ef1520875099d61cf4251e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:02:44 -0600 Subject: [PATCH 2/4] build(deps): bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 (#899) --- .github/workflows/go_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml index 6451d91..d91bc59 100644 --- a/.github/workflows/go_test.yml +++ b/.github/workflows/go_test.yml @@ -24,7 +24,7 @@ jobs: with: go-version: ${{ matrix.go-version }} - name: golangci-lint - uses: golangci/golangci-lint-action@v9.0.0 + uses: golangci/golangci-lint-action@v9.1.0 with: version: v2.4.0 - name: test From 974802ab3bd9b27dd2a6fe5afda4387966e26c14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:02:57 -0600 Subject: [PATCH 3/4] build(deps): bump actions/checkout from 5 to 6 (#898) --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/file_formatting.yml | 2 +- .github/workflows/go_test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9d6fcc6..0c3585f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/file_formatting.yml b/.github/workflows/file_formatting.yml index 45566ae..d7e4799 100644 --- a/.github/workflows/file_formatting.yml +++ b/.github/workflows/file_formatting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: verify example_test.go run: | grep "^func [a-z-A-Z]" example_test.go | sort -c diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml index d91bc59..b198132 100644 --- a/.github/workflows/go_test.yml +++ b/.github/workflows/go_test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Go uses: actions/setup-go@v6 with: From 9cc3be7cff94e954dfbcb123e0d0dc8459282da9 Mon Sep 17 00:00:00 2001 From: John Roesler Date: Wed, 26 Nov 2025 13:38:55 -0600 Subject: [PATCH 4/4] fix: calling start multiple times should no-op (#901) * fix: calling start multiple times should no-op * give test more time after start * add a mutex --- scheduler.go | 5 +++++ scheduler_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/scheduler.go b/scheduler.go index 382c55f..c57f826 100644 --- a/scheduler.go +++ b/scheduler.go @@ -824,6 +824,11 @@ func (s *scheduler) RemoveJob(id uuid.UUID) error { } func (s *scheduler) Start() { + if s.started.Load() { + s.logger.Warn("gocron: scheduler already started") + return + } + select { case <-s.shutdownCtx.Done(): case s.startCh <- struct{}{}: diff --git a/scheduler_test.go b/scheduler_test.go index e040576..3c71346 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -578,6 +578,41 @@ func TestScheduler_Shutdown(t *testing.T) { }) } +func TestScheduler_Start(t *testing.T) { + defer verifyNoGoroutineLeaks(t) + + t.Run("calling start multiple times is a no-op", func(t *testing.T) { + s := newTestScheduler(t) + + var counter int + var mu sync.Mutex + + _, err := s.NewJob( + DurationJob( + 100*time.Millisecond, + ), + NewTask( + func() { + mu.Lock() + counter++ + mu.Unlock() + }, + ), + ) + require.NoError(t, err) + + s.Start() + s.Start() + s.Start() + + time.Sleep(1000 * time.Millisecond) + + require.NoError(t, s.Shutdown()) + + assert.Contains(t, []int{9, 10}, counter) + }) +} + func TestScheduler_NewJob(t *testing.T) { defer verifyNoGoroutineLeaks(t) tests := []struct {