Repository: go-co-op/gocron
Pull request #288 (2022-02-08 15:51:57)

add SingletonModeAll() to enable for all jobs on scheduler
### What does this do?
adds the ability to enable SingletonMode on jobs at the scheduler level

### Which issue(s) does this PR fix/relate to?
<!--- Put `Resolves #XXX` here to auto-close the issue that your PR fixes (if such) --->
relates https://github.com/go-co-op/gocron/issues/279#issuecomment-1016803434

### 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/.gitignore b/.gitignore
index b909916..f6409f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 
 # Test binary, built with `go test -c`
 *.test
+local_testing
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
diff --git a/job.go b/job.go
index 049faef..32cd965 100644
--- a/job.go
+++ b/job.go
@@ -73,10 +73,10 @@ const (
 )
 
 // newJob creates a new Job with the provided interval
-func newJob(interval int, startImmediately bool) *Job {
+func newJob(interval int, startImmediately bool, singletonMode bool) *Job {
 	ctx, cancel := context.WithCancel(context.Background())
 	var zero int64
-	return &Job{
+	job := &Job{
 		interval: interval,
 		unit:     seconds,
 		lastRun:  time.Time{},
@@ -89,6 +89,10 @@ func newJob(interval int, startImmediately bool) *Job {
 		tags:              []string{},
 		startsImmediately: startImmediately,
 	}
+	if singletonMode {
+		job.SingletonMode()
+	}
+	return job
 }
 
 func (j *Job) neverRan() bool {
diff --git a/scheduler.go b/scheduler.go
index 8bf6e31..4e8404f 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -33,6 +33,7 @@ type Scheduler struct {
 	tagsUnique      bool // defines whether tags should be unique
 	updateJob       bool // so the scheduler knows to create a new job or update the current
 	waitForInterval bool // defaults jobs to waiting for first interval to start
+	singletonMode   bool // defaults all jobs to use SingletonMode()
 	jobCreated      bool // so the scheduler knows a job was created prior to calling Every or Cron
 }
 
@@ -664,13 +665,19 @@ func (s *Scheduler) LimitRunsTo(i int) *Scheduler {
 }
 
 // SingletonMode prevents a new job from starting if the prior job has not yet
-// completed it's run
+// completed its run
 func (s *Scheduler) SingletonMode() *Scheduler {
 	job := s.getCurrentJob()
 	job.SingletonMode()
 	return s
 }
 
+// SingletonModeAll prevents new jobs from starting if the prior instance of the
+// particular job has not yet completed its run
+func (s *Scheduler) SingletonModeAll() {
+	s.singletonMode = true
+}
+
 // TaskPresent checks if specific job's function was added to the scheduler.
 func (s *Scheduler) TaskPresent(j interface{}) bool {
 	for _, job := range s.Jobs() {
@@ -1121,7 +1128,7 @@ func (s *Scheduler) cron(cronExpression string, withSeconds bool) *Scheduler {
 }
 
 func (s *Scheduler) newJob(interval int) *Job {
-	return newJob(interval, !s.waitForInterval)
+	return newJob(interval, !s.waitForInterval, s.singletonMode)
 }
 
 // WaitForScheduleAll defaults the scheduler to create all


Test patch:
diff --git a/example_test.go b/example_test.go
index 05b9697..e018ba7 100644
--- a/example_test.go
+++ b/example_test.go
@@ -554,6 +554,13 @@ func ExampleScheduler_SingletonMode() {
 	_, _ = s.Every(1).Second().SingletonMode().Do(task)
 }
 
+func ExampleScheduler_SingletonModeAll() {
+	s := gocron.NewScheduler(time.UTC)
+	s.SingletonModeAll()
+
+	_, _ = s.Every(1).Second().Do(task)
+}
+
 func ExampleScheduler_StartAsync() {
 	s := gocron.NewScheduler(time.UTC)
 	_, _ = s.Every(3).Seconds().Do(task)
diff --git a/scheduler_test.go b/scheduler_test.go
index 873e75f..3f9757f 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -1098,6 +1098,46 @@ func TestScheduler_SingletonMode(t *testing.T) {
 
 }
 
+func TestScheduler_SingletonModeAll(t *testing.T) {
+
+	testCases := []struct {
+		description string
+		removeJob   bool
+	}{
+		{"with scheduler stop", false},
+		{"with job removal", true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+
+			s := NewScheduler(time.UTC)
+			s.SingletonModeAll()
+
+			var trigger int32
+
+			j, err := s.Every("100ms").Do(func() {
+				if atomic.LoadInt32(&trigger) == 1 {
+					t.Fatal("Restart should not occur")
+				}
+				atomic.AddInt32(&trigger, 1)
+				time.Sleep(300 * time.Millisecond)
+			})
+			require.NoError(t, err)
+
+			s.StartAsync()
+			time.Sleep(200 * time.Millisecond)
+
+			if tc.removeJob {
+				s.RemoveByReference(j)
+				time.Sleep(300 * time.Millisecond)
+			}
+			s.Stop()
+		})
+	}
+
+}
+
 func TestScheduler_LimitRunsTo(t *testing.T) {
 	t.Run("job added before starting scheduler", func(t *testing.T) {
 		semaphore := make(chan bool)