From 507947a71c9f914a3a8edc01a247e596b95cf4e6 Mon Sep 17 00:00:00 2001 From: John Roesler Date: Tue, 9 Feb 2021 09:11:05 -0600 Subject: [PATCH] add examples for all funcs, license tweak, func name clarifications (#121) * add examples for all funcs, license tweak, func name clarifications * add faq - canceling tasks. fix example test * check if job is in scheduler before scheduling --- LICENSE | 37 ++-- README.md | 121 +---------- example_test.go | 526 ++++++++++++++++++++++++++++++++++++++-------- gocron.go | 6 +- job.go | 7 +- scheduler.go | 53 +++-- scheduler_test.go | 25 ++- 7 files changed, 526 insertions(+), 249 deletions(-) diff --git a/LICENSE b/LICENSE index b9d8dd2f..3357d57d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,21 @@ -Copyright (c) 2014, 辣椒面 -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +MIT License -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +Copyright (c) 2014, 辣椒面 -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 24332297..edd7a907 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# goCron: A Golang Job Scheduling Package. +# gocron: A Golang Job Scheduling Package. -[![CI State](https://github.com/go-co-op/gocron/workflows/Go%20Test/badge.svg)](https://github.com/go-co-op/gocron/actions?query=workflow%3A"Go+Test") ![Go Report Card](https://goreportcard.com/badge/github.com/go-co-op/gocron) [![Go Doc](https://godoc.org/github.com/go-co-op/gocron?status.svg)](https://godoc.org/github.com/go-co-op/gocron) +[![CI State](https://github.com/go-co-op/gocron/workflows/Go%20Test/badge.svg)](https://github.com/go-co-op/gocron/actions?query=workflow%3A"Go+Test") ![Go Report Card](https://goreportcard.com/badge/github.com/go-co-op/gocron) [![Go Doc](https://godoc.org/github.com/go-co-op/gocron?status.svg)](https://pkg.go.dev/github.com/go-co-op/gocron) -goCron is a Golang job scheduling package which lets you run Go functions periodically at pre-determined interval using a simple, human-friendly syntax. +gocron is a Golang job scheduling package which lets you run Go functions periodically at pre-determined interval using a simple, human-friendly syntax. -goCron is a Golang implementation of Ruby module [clockwork](https://github.com/tomykaira/clockwork) and Python job scheduling package [schedule](https://github.com/dbader/schedule). +gocron is a Golang implementation of the Ruby module [clockwork](https://github.com/tomykaira/clockwork) and the Python job scheduling package [schedule](https://github.com/dbader/schedule). See also these two great articles: @@ -13,114 +13,14 @@ See also these two great articles: If you want to chat, you can find us at Slack! [](https://gophers.slack.com/archives/CQ7T0T1FW) -## Examples: +## FAQ -```go -package main +* Q: I'm running multiple pods on a distributed environment. How can I make a job not run once per pod causing duplication? +* A: We recommend using your own lock solution within the jobs themselves (you could use [Redis](https://redis.io/topics/distlock), for example) -import ( - "fmt" - "time" - - "github.com/go-co-op/gocron" -) - -func task() { - fmt.Println("I am running task.") -} - -func taskWithParams(a int, b string) { - fmt.Println(a, b) -} - -func main() { - // defines a new scheduler that schedules and runs jobs - s1 := gocron.NewScheduler(time.UTC) - - s1.Every(3).Seconds().Do(task) - - // scheduler starts running jobs and current thread continues to execute - s1.StartAsync() - - // Do jobs without params - s2 := gocron.NewScheduler(time.UTC) - s2.Every(1).Second().Do(task) - s2.Every(2).Seconds().Do(task) - s2.Every(1).Minute().Do(task) - s2.Every(2).Minutes().Do(task) - s2.Every(1).Hour().Do(task) - s2.Every(2).Hours().Do(task) - s2.Every(1).Day().Do(task) - s2.Every(2).Days().Do(task) - s2.Every(1).Week().Do(task) - s2.Every(2).Weeks().Do(task) - s2.Every(1).Month(time.Now().Day()).Do(task) - s2.Every(2).Months(15).Do(task) - - // check for errors - _, err := s2.Every(1).Day().At("bad-time").Do(task) - if err != nil { - log.Fatalf("error creating job: %v", err) - } - - // Do jobs with params - s2.Every(1).Second().Do(taskWithParams, 1, "hello") - - // Do Jobs with tags - // initialize tag - tag1 := []string{"tag1"} - tag2 := []string{"tag2"} - - s2.Every(1).Week().SetTag(tag1).Do(task) - s2.Every(1).Week().SetTag(tag2).Do(task) - - // Removing Job Based on Tag - s2.RemoveJobByTag("tag1") - - // Remove a Job after its last execution - j, _ := s2.Every(1).StartAt(time.Now().Add(30*time.Second)).Do(task) - j.LimitRunsTo(1) - j.RemoveAfterLastRun() - - // Do jobs on specific weekday - s2.Every(1).Monday().Do(task) - s2.Every(1).Thursday().Do(task) - - // Do a job at a specific time - 'hour:min:sec' - seconds optional - s2.Every(1).Day().At("10:30").Do(task) - s2.Every(1).Monday().At("18:30").Do(task) - s2.Every(1).Tuesday().At("18:30:59").Do(task) - s2.Every(1).Wednesday().At("1:01").Do(task) - - // Begin job at a specific date/time. - t := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC) - s2.Every(1).Hour().StartAt(t).Do(task) - - // Delay start of job - s2.Every(1).Hour().StartAt(time.Now().Add(time.Duration(1 * time.Hour)).Do(task) - - // NextRun gets the next running time - _, time := s2.NextRun() - fmt.Println(time) - - // Remove a specific job - s2.Remove(task) - - // Clear all scheduled jobs - s2.Clear() - - // stop our first scheduler (it still exists but doesn't run anymore) - s1.Stop() - - // executes the scheduler and blocks current thread - s2.StartBlocking() - - // this line is never reached -} -``` -### FAQ - * Q: I'm running multiple pods on a distributed environment. How can I make a job not run once per pod causing duplication? - * A: We recommend using your own lock solution within the jobs themselves (you could use [Redis](https://redis.io/topics/distlock), for example) +* Q: I've removed my job from the scheduler, but how can I stop a long-running job that has already been triggered? +* A: We recommend using a means of canceling your job, e.g. a `context.WithCancel()`. + --- Looking to contribute? Try to follow these guidelines: * Use issues for everything @@ -132,4 +32,5 @@ Looking to contribute? Try to follow these guidelines: * Suggesting new features or enhancements * Improving/fixing documentation --- + [Jetbrains](https://www.jetbrains.com/?from=gocron) supports this project with GoLand licenses. We appreciate their support for free and open source software! diff --git a/example_test.go b/example_test.go index f4909609..3fa916d4 100644 --- a/example_test.go +++ b/example_test.go @@ -9,14 +9,140 @@ import ( var task = func() {} +// --------------------------------------------------------------------- +// ----------------------JOB-FUNCTIONS---------------------------------- +// --------------------------------------------------------------------- + +func ExampleJob_Err() { + s := gocron.NewScheduler(time.UTC) + s.Every(1).Day().At("bad time") + j := s.Jobs()[0] + fmt.Println(j.Err()) + // Output: + // time format error +} + +func ExampleJob_LastRun() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Second().Do(task) + s.StartAsync() + + fmt.Println("Last run:", job.LastRun()) +} + +func ExampleJob_LimitRunsTo() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Second().Do(task) + job.LimitRunsTo(2) + s.StartAsync() +} + +func ExampleJob_NextRun() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Second().Do(task) + go func() { + for { + fmt.Println("Next run", job.NextRun()) + time.Sleep(time.Second) + } + }() + s.StartAsync() +} + +func ExampleJob_RemoveAfterLastRun() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Second().Do(task) + job.LimitRunsTo(1) + job.RemoveAfterLastRun() + s.StartAsync() +} + +func ExampleJob_RunCount() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Second().Do(task) + go func() { + for { + fmt.Println("Run count", job.RunCount()) + time.Sleep(time.Second) + } + }() + s.StartAsync() +} + +func ExampleJob_ScheduledAtTime() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Day().At("10:30").Do(task) + s.StartAsync() + fmt.Println(job.ScheduledAtTime()) + // Output: + // 10:30 +} + +func ExampleJob_ScheduledTime() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every(1).Day().At("10:30").Do(task) + s.StartAsync() + fmt.Println(job.ScheduledTime()) +} + +func ExampleJob_Tag() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every("1s").Do(task) + + job.Tag("tag1", "tag2", "tag3") + s.StartAsync() + fmt.Println(job.Tags()) + // Output: + // [tag1 tag2 tag3] +} + +func ExampleJob_Tags() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every("1s").Do(task) + + job.Tag("tag1", "tag2", "tag3") + s.StartAsync() + fmt.Println(job.Tags()) + // Output: + // [tag1 tag2 tag3] +} + +func ExampleJob_Untag() { + s := gocron.NewScheduler(time.UTC) + job, _ := s.Every("1s").Do(task) + + job.Tag("tag1", "tag2", "tag3") + s.StartAsync() + fmt.Println(job.Tags()) + job.Untag("tag2") + fmt.Println(job.Tags()) + // Output: + // [tag1 tag2 tag3] + // [tag1 tag3] +} + +func ExampleJob_Weekday() { + s := gocron.NewScheduler(time.UTC) + weeklyJob, _ := s.Every(1).Week().Monday().Do(task) + weekday, _ := weeklyJob.Weekday() + fmt.Println(weekday) + + dailyJob, _ := s.Every(1).Day().Do(task) + _, err := dailyJob.Weekday() + fmt.Println(err) + // Output: + // Monday + // job not scheduled weekly on a weekday +} + // --------------------------------------------------------------------- // -------------------SCHEDULER-FUNCTIONS------------------------------- // --------------------------------------------------------------------- -func ExampleScheduler_Location() { +func ExampleScheduler_At() { s := gocron.NewScheduler(time.UTC) - fmt.Println(s.Location()) - // Output: UTC + _, _ = s.Every(1).Day().At("10:30").Do(task) + _, _ = s.Every(1).Monday().At("10:30:01").Do(task) } func ExampleScheduler_ChangeLocation() { @@ -34,16 +160,41 @@ func ExampleScheduler_ChangeLocation() { // America/Los_Angeles } -func ExampleScheduler_StartBlocking() { +func ExampleScheduler_Clear() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(3).Seconds().Do(task) - s.StartBlocking() + _, _ = s.Every(1).Second().Do(task) + _, _ = s.Every(1).Minute().Do(task) + _, _ = s.Every(1).Month(1).Do(task) + fmt.Println(len(s.Jobs())) // Print the number of jobs before clearing + s.Clear() // Clear all the jobs + fmt.Println(len(s.Jobs())) // Print the number of jobs after clearing + s.StartAsync() + // Output: + // 3 + // 0 } -func ExampleScheduler_StartAsync() { +func ExampleScheduler_Day() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(3).Seconds().Do(task) + + _, _ = s.Every("24h").Do(task) + _, _ = s.Every(1).Day().Do(task) + _, _ = s.Every(1).Days().Do(task) +} + +func ExampleScheduler_Days() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every("24h").Do(task) + _, _ = s.Every(1).Day().Do(task) + _, _ = s.Every(1).Days().Do(task) +} + +func ExampleScheduler_Do() { + s := gocron.NewScheduler(time.UTC) + j, err := s.Every(1).Second().Do(task) s.StartAsync() + fmt.Printf("Job: %v, Error: %v", j, err) } func ExampleScheduler_Every() { @@ -54,68 +205,75 @@ func ExampleScheduler_Every() { s.StartAsync() } -func ExampleScheduler_StartAt() { +func ExampleScheduler_Friday() { s := gocron.NewScheduler(time.UTC) - specificTime := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC) - _, _ = s.Every(1).Hour().StartAt(specificTime).Do(task) - s.StartBlocking() + j, _ := s.Every(1).Day().Friday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Friday } -func ExampleScheduler_Stop() { +func ExampleScheduler_Hour() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(1).Second().Do(task) - s.StartAsync() - time.Sleep(time.Second * 5) - s.Stop() + + _, _ = s.Every("1h").Do(task) + _, _ = s.Every(1).Hour().Do(task) + _, _ = s.Every(1).Hours().Do(task) } -func ExampleScheduler_At() { +func ExampleScheduler_Hours() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(1).Day().At("10:30").Do(task) - _, _ = s.Every(1).Monday().At("10:30:01").Do(task) + + _, _ = s.Every("1h").Do(task) + _, _ = s.Every(1).Hour().Do(task) + _, _ = s.Every(1).Hours().Do(task) } -func ExampleScheduler_RemoveJobByTag() { +func ExampleScheduler_IsRunning() { s := gocron.NewScheduler(time.UTC) - tag1 := []string{"tag1"} - tag2 := []string{"tag2"} - _, _ = s.Every(1).Week().SetTag(tag1).Do(task) - _, _ = s.Every(1).Week().SetTag(tag2).Do(task) + + _, _ = s.Every("1s").Do(task) + fmt.Println(s.IsRunning()) s.StartAsync() - _ = s.RemoveJobByTag("tag1") + fmt.Println(s.IsRunning()) + // Output: + // false + // true } -func ExampleScheduler_NextRun() { +func ExampleScheduler_Jobs() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(1).Day().At("10:30").Do(task) - s.StartAsync() - _, t := s.NextRun() - fmt.Println(t.Format("15:04")) // print only the hour and minute (hh:mm) - // Output: 10:30 + + _, _ = s.Every("1s").Do(task) + _, _ = s.Every("1s").Do(task) + _, _ = s.Every("1s").Do(task) + fmt.Println(len(s.Jobs())) + // Output: + // 3 } -func ExampleScheduler_Clear() { +func ExampleScheduler_Len() { s := gocron.NewScheduler(time.UTC) - _, _ = s.Every(1).Second().Do(task) - _, _ = s.Every(1).Minute().Do(task) - _, _ = s.Every(1).Month(1).Do(task) - fmt.Println(len(s.Jobs())) // Print the number of jobs before clearing - s.Clear() // Clear all the jobs - fmt.Println(len(s.Jobs())) // Print the number of jobs after clearing - s.StartAsync() + + _, _ = s.Every("1s").Do(task) + _, _ = s.Every("1s").Do(task) + _, _ = s.Every("1s").Do(task) + fmt.Println(s.Len()) // Output: // 3 - // 0 } -func ExampleScheduler_Seconds() { +func ExampleScheduler_Less() { s := gocron.NewScheduler(time.UTC) - // the default unit is seconds - // these are all the same - _, _ = s.Every(1).Do(task) - _, _ = s.Every(1).Second().Do(task) - _, _ = s.Every(1).Seconds().Do(task) + _, _ = s.Every("1s").Do(task) + _, _ = s.Every("2s").Do(task) + s.StartAsync() + fmt.Println(s.Less(0, 1)) + // Output: + // true } func ExampleScheduler_LimitRunsTo() { @@ -129,6 +287,74 @@ func ExampleScheduler_LimitRunsTo() { // 1 } +func ExampleScheduler_Location() { + s := gocron.NewScheduler(time.UTC) + fmt.Println(s.Location()) + // Output: + // UTC +} + +func ExampleScheduler_Minute() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every("1m").Do(task) + _, _ = s.Every(1).Minute().Do(task) + _, _ = s.Every(1).Minutes().Do(task) +} + +func ExampleScheduler_Minutes() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every("1m").Do(task) + _, _ = s.Every(1).Minute().Do(task) + _, _ = s.Every(1).Minutes().Do(task) +} + +func ExampleScheduler_Monday() { + s := gocron.NewScheduler(time.UTC) + j, _ := s.Every(1).Day().Monday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Monday +} + +func ExampleScheduler_Month() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Month(1).Do(task) + _, _ = s.Every(1).Months(1).Do(task) +} + +func ExampleScheduler_Months() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Month(1).Do(task) + _, _ = s.Every(1).Months(1).Do(task) +} + +func ExampleScheduler_NextRun() { + s := gocron.NewScheduler(time.UTC) + _, _ = s.Every(1).Day().At("10:30").Do(task) + s.StartAsync() + _, t := s.NextRun() + // print only the hour and minute (hh:mm) + fmt.Println(t.Format("15:04")) + // Output: + // 10:30 +} + +func ExampleScheduler_Remove() { + s := gocron.NewScheduler(time.UTC) + _, _ = s.Every(1).Week().Do(task) + s.StartAsync() + s.Remove(task) + fmt.Println(s.Len()) + // Output: + // 0 +} + func ExampleScheduler_RemoveAfterLastRun() { s := gocron.NewScheduler(time.UTC) @@ -142,64 +368,194 @@ func ExampleScheduler_RemoveAfterLastRun() { // 0 } -// --------------------------------------------------------------------- -// ----------------------JOB-FUNCTIONS---------------------------------- -// --------------------------------------------------------------------- +func ExampleScheduler_RemoveByReference() { + s := gocron.NewScheduler(time.UTC) -func ExampleJob_ScheduledTime() { + j, _ := s.Every(1).Week().Do(task) + _, _ = s.Every(1).Week().Do(task) + s.StartAsync() + s.RemoveByReference(j) + fmt.Println(s.Len()) + // Output: + // 1 +} + +func ExampleScheduler_RemoveByTag() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Day().At("10:30").Do(task) - fmt.Println(job.ScheduledAtTime()) - // Output: 10:30 + _, _ = s.Every(1).Week().Tag("tag1").Do(task) + _, _ = s.Every(1).Week().Tag("tag2").Do(task) + s.StartAsync() + _ = s.RemoveByTag("tag1") + fmt.Println(s.Len()) + // Output: + // 1 } -func ExampleJob_LimitRunsTo() { +func ExampleScheduler_RunAll() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Second().Do(task) - job.LimitRunsTo(2) + _, _ = s.Every(1).Day().At("10:00").Do(task) + _, _ = s.Every(2).Day().At("10:00").Do(task) + _, _ = s.Every(3).Day().At("10:00").Do(task) s.StartAsync() + s.RunAll() } -func ExampleJob_LastRun() { +func ExampleScheduler_RunAllWithDelay() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Second().Do(task) - go func() { - for { - fmt.Println("Last run", job.LastRun()) - time.Sleep(time.Second) - } - }() + _, _ = s.Every(1).Day().At("10:00").Do(task) + _, _ = s.Every(2).Day().At("10:00").Do(task) + _, _ = s.Every(3).Day().At("10:00").Do(task) + s.StartAsync() + s.RunAllWithDelay(10 * time.Second) +} + +func ExampleScheduler_Saturday() { + s := gocron.NewScheduler(time.UTC) + j, _ := s.Every(1).Day().Saturday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Saturday +} + +func ExampleScheduler_Second() { + s := gocron.NewScheduler(time.UTC) + + // the default unit is seconds + // these are all the same + _, _ = s.Every(1).Do(task) + _, _ = s.Every(1).Second().Do(task) + _, _ = s.Every(1).Seconds().Do(task) +} + +func ExampleScheduler_Seconds() { + s := gocron.NewScheduler(time.UTC) + + // the default unit is seconds + // these are all the same + _, _ = s.Every(1).Do(task) + _, _ = s.Every(1).Second().Do(task) + _, _ = s.Every(1).Seconds().Do(task) +} + +func ExampleScheduler_StartBlocking() { + s := gocron.NewScheduler(time.UTC) + _, _ = s.Every(3).Seconds().Do(task) s.StartBlocking() } -func ExampleJob_NextRun() { +func ExampleScheduler_StartAsync() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Second().Do(task) - go func() { - for { - fmt.Println("Next run", job.NextRun()) - time.Sleep(time.Second) - } - }() + _, _ = s.Every(3).Seconds().Do(task) s.StartAsync() } -func ExampleJob_RunCount() { +func ExampleScheduler_StartAt() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Second().Do(task) - go func() { - for { - fmt.Println("Run count", job.RunCount()) - time.Sleep(time.Second) - } - }() + specificTime := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC) + _, _ = s.Every(1).Hour().StartAt(specificTime).Do(task) + s.StartBlocking() +} + +func ExampleScheduler_Stop() { + s := gocron.NewScheduler(time.UTC) + _, _ = s.Every(1).Second().Do(task) s.StartAsync() + s.Stop() + fmt.Println(s.IsRunning()) + // Output: + // false } -func ExampleJob_RemoveAfterLastRun() { +func ExampleScheduler_Sunday() { s := gocron.NewScheduler(time.UTC) - job, _ := s.Every(1).Second().Do(task) - job.LimitRunsTo(1) - job.RemoveAfterLastRun() + j, _ := s.Every(1).Day().Sunday().Do(task) s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Sunday +} + +func ExampleScheduler_Swap() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Tag("tag1").Do(task) + _, _ = s.Every(1).Tag("tag2").Day().Monday().Do(task) + fmt.Println(s.Jobs()[0].Tags()[0], s.Jobs()[1].Tags()[0]) + s.Swap(0, 1) + fmt.Println(s.Jobs()[0].Tags()[0], s.Jobs()[1].Tags()[0]) + // Output: + // tag1 tag2 + // tag2 tag1 +} + +func ExampleScheduler_Tag() { + s := gocron.NewScheduler(time.UTC) + + j, _ := s.Every(1).Week().Tag("tag").Do(task) + fmt.Println(j.Tags()) + // Output: + // [tag] +} + +func ExampleScheduler_TaskPresent() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Do(task) + fmt.Println(s.TaskPresent(task)) + // Output: + // true +} + +func ExampleScheduler_Thursday() { + s := gocron.NewScheduler(time.UTC) + j, _ := s.Every(1).Day().Thursday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Thursday +} + +func ExampleScheduler_Tuesday() { + s := gocron.NewScheduler(time.UTC) + j, _ := s.Every(1).Day().Tuesday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Tuesday +} + +func ExampleScheduler_Wednesday() { + s := gocron.NewScheduler(time.UTC) + j, _ := s.Every(1).Day().Wednesday().Do(task) + s.StartAsync() + wd, _ := j.Weekday() + fmt.Println(wd) + // Output: + // Wednesday +} + +func ExampleScheduler_Week() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Week().Do(task) + _, _ = s.Every(1).Weeks().Do(task) +} + +func ExampleScheduler_Weekday() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Week().Weekday(time.Monday).Do(task) + _, _ = s.Every(1).Weeks().Weekday(time.Tuesday).Do(task) +} + +func ExampleScheduler_Weeks() { + s := gocron.NewScheduler(time.UTC) + + _, _ = s.Every(1).Week().Do(task) + _, _ = s.Every(1).Weeks().Do(task) } diff --git a/gocron.go b/gocron.go index fca8f4be..52538053 100644 --- a/gocron.go +++ b/gocron.go @@ -1,13 +1,9 @@ // Package gocron : A Golang Job Scheduling Package. // // An in-process scheduler for periodic jobs that uses the builder pattern -// for configuration. Schedule lets you run Golang functions periodically +// for configuration. gocron lets you run Golang functions periodically // at pre-determined intervals using a simple, human-friendly syntax. // -// Copyright 2014 Jason Lyu. jasonlvhit@gmail.com . -// All rights reserved. -// Use of this source code is governed by a BSD-style . -// license that can be found in the LICENSE file. package gocron import ( diff --git a/job.go b/job.go index 0866b0f3..c5a3de20 100644 --- a/job.go +++ b/job.go @@ -115,11 +115,10 @@ func (j *Job) Err() error { // Tag allows you to add arbitrary labels to a Job that do not // impact the functionality of the Job -func (j *Job) Tag(t string, others ...string) { +func (j *Job) Tag(tags ...string) { j.Lock() defer j.Unlock() - j.tags = append(j.tags, t) - j.tags = append(j.tags, others...) + j.tags = append(j.tags, tags...) } // Untag removes a tag from a Job @@ -240,6 +239,8 @@ func (j *Job) RunCount() int { } func (j *Job) stopTimer() { + j.Lock() + defer j.Unlock() if j.timer != nil { j.timer.Stop() } diff --git a/scheduler.go b/scheduler.go index 388f3f0a..e631adf3 100644 --- a/scheduler.go +++ b/scheduler.go @@ -92,15 +92,18 @@ func (s *Scheduler) Len() int { return len(s.jobs) } -// Swap +// Swap places each job into the other job's position given +// the provided job indexes. func (s *Scheduler) Swap(i, j int) { s.jobsMutex.Lock() defer s.jobsMutex.Unlock() s.jobs[i], s.jobs[j] = s.jobs[j], s.jobs[i] } -func (s *Scheduler) Less(i, j int) bool { - return s.Jobs()[j].NextRun().Unix() >= s.Jobs()[i].NextRun().Unix() +// Less compares the next run of jobs based on their index. +// Returns true if the second job is after the first. +func (s *Scheduler) Less(first, second int) bool { + return s.Jobs()[second].NextRun().Unix() >= s.Jobs()[first].NextRun().Unix() } // ChangeLocation changes the default time location @@ -122,6 +125,10 @@ func (s *Scheduler) scheduleNextRun(job *Job) { now := s.now() lastRun := job.LastRun() + if !s.jobPresent(job) { + return + } + if job.getStartsImmediately() { s.run(job) job.setStartsImmediately(false) @@ -295,7 +302,10 @@ func (s *Scheduler) NextRun() (*Job, time.Time) { return s.Jobs()[0], s.Jobs()[0].NextRun() } -// Every schedules a new periodic Job with interval +// Every schedules a new periodic Job with an interval. +// Interval can be an int, time.Duration or a string that +// parses with time.ParseDuration(). +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". func (s *Scheduler) Every(interval interface{}) *Scheduler { switch interval := interval.(type) { case int: @@ -340,15 +350,21 @@ func (s *Scheduler) RunAll() { s.RunAllWithDelay(0) } -// RunAllWithDelay runs all Jobs with delay seconds -func (s *Scheduler) RunAllWithDelay(d int) { +// RunAllWithDelay runs all jobs with the provided delay in between each job +func (s *Scheduler) RunAllWithDelay(d time.Duration) { for _, job := range s.Jobs() { s.run(job) - s.time.Sleep(time.Duration(d) * time.Second) + s.time.Sleep(d) } } // Remove specific Job j by function +// +// Removing a job stops that job's timer. However, if a job has already +// been started by by the job's timer before being removed, there is no way to stop +// it through gocron as https://pkg.go.dev/time#Timer.Stop explains. +// The job function would need to have implemented a means of +// stopping, e.g. using a context.WithCancel(). func (s *Scheduler) Remove(j interface{}) { s.removeByCondition(func(someJob *Job) bool { return someJob.jobFunc == getFunctionName(j) @@ -358,6 +374,8 @@ func (s *Scheduler) Remove(j interface{}) { // RemoveByReference removes specific Job j by reference func (s *Scheduler) RemoveByReference(j *Job) { s.removeByCondition(func(someJob *Job) bool { + j.RLock() + defer j.RUnlock() return someJob == j }) } @@ -374,8 +392,8 @@ func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) { s.setJobs(retainedJobs) } -// RemoveJobByTag will Remove Jobs by Tag -func (s *Scheduler) RemoveJobByTag(tag string) error { +// RemoveByTag will remove a job by a given tag. +func (s *Scheduler) RemoveByTag(tag string) error { jobindex, err := s.findJobsIndexByTag(tag) if err != nil { return err @@ -419,8 +437,8 @@ func (s *Scheduler) RemoveAfterLastRun() *Scheduler { return s } -// Scheduled checks if specific Job j was already added -func (s *Scheduler) Scheduled(j interface{}) bool { +// TaskPresent checks if specific job's function was added to the scheduler. +func (s *Scheduler) TaskPresent(j interface{}) bool { for _, job := range s.Jobs() { if job.jobFunc == getFunctionName(j) { return true @@ -429,6 +447,15 @@ func (s *Scheduler) Scheduled(j interface{}) bool { return false } +func (s *Scheduler) jobPresent(j *Job) bool { + for _, job := range s.Jobs() { + if job == j { + return true + } + } + return false +} + // Clear clear all Jobs from this scheduler func (s *Scheduler) Clear() { s.setJobs(make([]*Job, 0)) @@ -489,8 +516,8 @@ func (s *Scheduler) At(t string) *Scheduler { return s } -// SetTag will add tag when creating a job -func (s *Scheduler) SetTag(t []string) *Scheduler { +// Tag will add a tag when creating a job. +func (s *Scheduler) Tag(t ...string) *Scheduler { job := s.getCurrentJob() job.tags = t return s diff --git a/scheduler_test.go b/scheduler_test.go index 23ffae46..5a8aceef 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -195,15 +195,14 @@ func TestScheduled(t *testing.T) { s := NewScheduler(time.UTC) _, err := s.Every(1).Second().Do(task) require.NoError(t, err) - assert.True(t, s.Scheduled(task)) + assert.True(t, s.TaskPresent(task)) }) t.Run("with tag", func(t *testing.T) { s := NewScheduler(time.UTC) - tag := []string{"my_custom_tag"} - _, err := s.Every(1).Hour().SetTag(tag).Do(task) + _, err := s.Every(1).Hour().Tag("my_custom_tag").Do(task) require.NoError(t, err) - assert.True(t, s.Scheduled(task)) + assert.True(t, s.TaskPresent(task)) }) } @@ -324,7 +323,7 @@ func TestWeekdayAt(t *testing.T) { }) } -func TestRemove(t *testing.T) { +func TestScheduler_Remove(t *testing.T) { t.Run("remove from non-running", func(t *testing.T) { s := NewScheduler(time.UTC) @@ -404,25 +403,25 @@ func TestRemoveByTag(t *testing.T) { s := NewScheduler(time.UTC) // Creating 2 Jobs with Unique tags - tag1 := []string{"tag one"} - tag2 := []string{"tag two"} - _, err := s.Every(1).Second().SetTag(tag1).Do(taskWithParams, 1, "hello") // index 0 + tag1 := "tag one" + tag2 := "tag two" + _, err := s.Every(1).Second().Tag(tag1).Do(taskWithParams, 1, "hello") // index 0 require.NoError(t, err) - _, err = s.Every(1).Second().SetTag(tag2).Do(taskWithParams, 2, "world") // index 1 + _, err = s.Every(1).Second().Tag(tag2).Do(taskWithParams, 2, "world") // index 1 require.NoError(t, err) // check Jobs()[0] tags is equal with tag "tag one" (tag1) - assert.Equal(t, s.Jobs()[0].Tags(), tag1, "Job With Tag 'tag one' is removed from index 0") + assert.Equal(t, s.Jobs()[0].Tags()[0], tag1, "Job With Tag 'tag one' is removed from index 0") - err = s.RemoveJobByTag("tag one") + err = s.RemoveByTag("tag one") require.NoError(t, err) assert.Equal(t, 1, s.Len(), "Incorrect number of jobs after removing 1 job") // check Jobs()[0] tags is equal with tag "tag two" (tag2) after removing "tag one" - assert.Equal(t, s.Jobs()[0].Tags(), tag2, "Job With Tag 'tag two' is removed from index 0") + assert.Equal(t, s.Jobs()[0].Tags()[0], tag2, "Job With Tag 'tag two' is removed from index 0") // Removing Non Existent Job with "tag one" because already removed above (will not removing any jobs because tag not match) - err = s.RemoveJobByTag("tag one") + err = s.RemoveByTag("tag one") assert.EqualError(t, err, ErrJobNotFoundWithTag.Error()) }