From 1ee2481e4848aa1dad1d5d4f088cd9fc3887a725 Mon Sep 17 00:00:00 2001 From: iyashjayesh Date: Tue, 21 Oct 2025 20:10:08 +0530 Subject: [PATCH] update the functionality for singleton mode jobs --- example_test.go | 50 ++++++++++++++++++++++++------------------------- executor.go | 15 ++++++++++++--- job.go | 2 +- job_test.go | 7 ++++++- scheduler.go | 2 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/example_test.go b/example_test.go index fb670fd..341ea2a 100644 --- a/example_test.go +++ b/example_test.go @@ -862,6 +862,31 @@ func ExampleWithIdentifier() { // 87b95dfc-3e71-11ef-9454-0242ac120002 } +func ExampleWithIntervalFromCompletion() { + s, _ := gocron.NewScheduler() + defer func() { _ = s.Shutdown() }() + + _, _ = s.NewJob( + gocron.DurationJob( + 5*time.Minute, + ), + gocron.NewTask( + func() { + time.Sleep(30 * time.Second) + }, + ), + gocron.WithIntervalFromCompletion(), + ) + + // Without WithIntervalFromCompletion (default behavior): + // If the job starts at 00:00 and completes at 00:00:30, + // the next job starts at 00:05:00 (only 4m30s rest). + + // With WithIntervalFromCompletion: + // If the job starts at 00:00 and completes at 00:00:30, + // the next job starts at 00:05:30 (full 5m rest). +} + func ExampleWithLimitConcurrentJobs() { _, _ = gocron.NewScheduler( gocron.WithLimitConcurrentJobs( @@ -1016,31 +1041,6 @@ func ExampleWithSingletonMode() { ) } -func ExampleWithIntervalFromCompletion() { - s, _ := gocron.NewScheduler() - defer func() { _ = s.Shutdown() }() - - _, _ = s.NewJob( - gocron.DurationJob( - 5*time.Minute, - ), - gocron.NewTask( - func() { - time.Sleep(30 * time.Second) - }, - ), - gocron.WithIntervalFromCompletion(), - ) - - // Without WithIntervalFromCompletion (default behavior): - // If the job starts at 00:00 and completes at 00:00:30, - // the next job starts at 00:05:00 (only 4m30s rest). - - // With WithIntervalFromCompletion: - // If the job starts at 00:00 and completes at 00:00:30, - // the next job starts at 00:05:30 (full 5m rest). -} - func ExampleWithStartAt() { s, _ := gocron.NewScheduler() defer func() { _ = s.Shutdown() }() diff --git a/executor.go b/executor.go index 5e00f45..c5c2a03 100644 --- a/executor.go +++ b/executor.go @@ -200,7 +200,10 @@ func (e *executor) start() { select { case runner.rescheduleLimiter <- struct{}{}: runner.in <- jIn - e.sendOutForRescheduling(&jIn) + // For intervalFromCompletion, skip rescheduling here - it will happen after job completes + if !j.intervalFromCompletion { + e.sendOutForRescheduling(&jIn) + } default: // runner is busy, reschedule the work for later // which means we just skip it here and do nothing @@ -210,7 +213,10 @@ func (e *executor) start() { } else { // wait mode, fill up that queue (buffered channel, so it's ok) runner.in <- jIn - e.sendOutForRescheduling(&jIn) + // For intervalFromCompletion, skip rescheduling here - it will happen after job completes + if !j.intervalFromCompletion { + e.sendOutForRescheduling(&jIn) + } } } else { select { @@ -345,7 +351,10 @@ func (e *executor) singletonModeRunner(name string, in chan jobIn, wg *waitGroup // need to set shouldSendOut = false here, as there is a duplicative call to sendOutForRescheduling // inside the runJob function that needs to be skipped. sendOutForRescheduling is previously called // when the job is sent to the singleton mode runner. - jIn.shouldSendOut = false + // Exception: for intervalFromCompletion, we want rescheduling to happen AFTER job completion + if !j.intervalFromCompletion { + jIn.shouldSendOut = false + } e.runJob(*j, jIn) } diff --git a/job.go b/job.go index f79a06f..b89f10d 100644 --- a/job.go +++ b/job.go @@ -701,7 +701,7 @@ func WithSingletonMode(mode LimitMode) JobOption { // Note: This option only makes sense with interval-based jobs (DurationJob, DurationRandomJob). // For time-based jobs (CronJob, DailyJob, etc.) that run at specific times, this option // will be ignored as those jobs are inherently scheduled at fixed times. -// +// // Example: // // s.NewJob( diff --git a/job_test.go b/job_test.go index a07f4e6..7128667 100644 --- a/job_test.go +++ b/job_test.go @@ -964,13 +964,18 @@ func TestWithIntervalFromCompletion_LongRunningJob(t *testing.T) { // Execution 1: 0s-6s, wait 3s → next at 9s // Execution 2: 9s-15s, wait 3s → next at 18s // Need to wait at least 16 seconds for 2 executions + buffer - time.Sleep(20 * time.Second) + time.Sleep(22 * time.Second) mu.Lock() defer mu.Unlock() require.GreaterOrEqual(t, len(executions), 2, "Expected at least 2 executions") + if len(executions) < 2 { + t.Logf("Only got %d execution(s), skipping gap assertion", len(executions)) + return + } + prev := executions[0] curr := executions[1] diff --git a/scheduler.go b/scheduler.go index a3f4395..274f1e2 100644 --- a/scheduler.go +++ b/scheduler.go @@ -347,7 +347,7 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) { } var scheduleFrom time.Time - + // If intervalFromCompletion is enabled, calculate the next run time // from when the job completed (lastRun) rather than when it was scheduled. if j.intervalFromCompletion {