diff --git a/go.mod b/go.mod index 739449315..e1cd2f62a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect github.com/fatih/color v1.9.0 - github.com/go-co-op/gocron v0.3.3 + github.com/go-co-op/gocron v1.0.0 github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 diff --git a/go.sum b/go.sum index 63935de76..e93f79a13 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,8 @@ github.com/go-co-op/gocron v0.3.2 h1:dij1B64q5euTenSBBv7Aoaqtr/pOEDmPv3Iz8qhQ8J0 github.com/go-co-op/gocron v0.3.2/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-co-op/gocron v0.3.3 h1:QnarcMZWWKrEP25uCbtDiLsnnGw+PhCjL3wNITdWJOs= github.com/go-co-op/gocron v0.3.3/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= +github.com/go-co-op/gocron v1.0.0 h1:ALryRuOUIOTFOIqq8ToKS/1nFm6JAjd3rz1o6GXLK8Q= +github.com/go-co-op/gocron v1.0.0/go.mod h1:9rZ2ZJCai6lz7JG5b+AdRsNnxjirM/Vc+gss/FJW8eU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -629,6 +631,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -850,6 +854,8 @@ gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/go-co-op/gocron/.gitignore b/vendor/github.com/go-co-op/gocron/.gitignore index 1d1419d95..b909916eb 100644 --- a/vendor/github.com/go-co-op/gocron/.gitignore +++ b/vendor/github.com/go-co-op/gocron/.gitignore @@ -1,26 +1,18 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll *.so +*.dylib -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* +# Test binary, built with `go test -c` +*.test -_testmain.go +# Output of the go coverage tool, specifically when used with LiteIDE +*.out -*.exe -*.test +# Dependency directories (remove the comment below to include it) +vendor/ # IDE project files .idea diff --git a/vendor/github.com/go-co-op/gocron/.golangci.yaml b/vendor/github.com/go-co-op/gocron/.golangci.yaml new file mode 100644 index 000000000..7568db3bb --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/.golangci.yaml @@ -0,0 +1,49 @@ +run: + timeout: 2m + issues-exit-code: 1 + tests: true + +issues: + max-same-issues: 100 + exclude-rules: + - path: _test\.go + linters: + - bodyclose + - errcheck + - gosec + +linters: + enable: + - bodyclose + - deadcode + - errcheck + - gofmt + - golint + - gosec + - gosimple + - govet + - ineffassign + - misspell + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" + format: colored-line-number + # print lines of code with issue, default is true + print-issued-lines: true + # print linter name in the end of issue text, default is true + print-linter-name: true + # make issues output unique by line, default is true + uniq-by-line: true + # add a prefix to the output file references; default is no prefix + path-prefix: "" + # sorts results by: filepath, line and column + sort-results: true + +linters-settings: + golint: + min-confidence: 0.8 diff --git a/vendor/github.com/go-co-op/gocron/CODE_OF_CONDUCT.md b/vendor/github.com/go-co-op/gocron/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..7d913b55b --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone. And we mean everyone! + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and kind language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team initially on Slack to coordinate private communication. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/github.com/go-co-op/gocron/CONTRIBUTING.md b/vendor/github.com/go-co-op/gocron/CONTRIBUTING.md new file mode 100644 index 000000000..46f1ecbaa --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing to gocron + +Thank you for coming to contribute to gocron! We welcome new ideas, PRs and general feedback. + +## Reporting Bugs + +If you find a bug then please let the project know by opening an issue after doing the following: + +- Do a quick search of the existing issues to make sure the bug isn't already reported +- Try and make a minimal list of steps that can reliably reproduce the bug you are experiencing +- Collect as much information as you can to help identify what the issue is (project version, configuration files, etc) + +## Suggesting Enhancements + +If you have a use case that you don't see a way to support yet, we would welcome the feedback in an issue. Before opening the issue, please consider: + +- Is this a common use case? +- Is it simple to understand? + +You can help us out by doing the following before raising a new issue: + +- Check that the feature hasn't been requested already by searching existing issues +- Try and reduce your enhancement into a single, concise and deliverable request, rather than a general idea +- Explain your own use cases as the basis of the request + +## Adding Features + +Pull requests are always welcome. However, before going through the trouble of implementing a change it's worth creating a bug or feature request issue. +This allows us to discuss the changes and make sure they are a good fit for the project. + +Please always make sure a pull request has been: + +- Unit tested with `make test` +- Linted with `make lint` +- Vetted with `make vet` +- Formatted with `make fmt` or validated with `make check-fmt` + +## Writing Tests + +Tests should follow the [table driven test pattern](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go). See other tests in the code base for additional examples. diff --git a/vendor/github.com/go-co-op/gocron/LICENSE b/vendor/github.com/go-co-op/gocron/LICENSE index b9d8dd2f5..3357d57d7 100644 --- a/vendor/github.com/go-co-op/gocron/LICENSE +++ b/vendor/github.com/go-co-op/gocron/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/vendor/github.com/go-co-op/gocron/Makefile b/vendor/github.com/go-co-op/gocron/Makefile new file mode 100644 index 000000000..08cbf82b9 --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/Makefile @@ -0,0 +1,33 @@ +.PHONY: fmt check-fmt lint vet test + +GO_PKGS := $(shell go list -f {{.Dir}} ./...) + +fmt: + @go list -f {{.Dir}} ./... | xargs -I{} gofmt -w -s {} + +check-fmt: + @echo "Checking formatting..." + @FMT="0"; \ + for pkg in $(GO_PKGS); do \ + OUTPUT=`gofmt -l $$pkg/*.go`; \ + if [ -n "$$OUTPUT" ]; then \ + echo "$$OUTPUT"; \ + FMT="1"; \ + fi; \ + done ; \ + if [ "$$FMT" -eq "1" ]; then \ + echo "Problem with formatting in files above."; \ + exit 1; \ + else \ + echo "Success - way to run gofmt!"; \ + fi + +lint: +# Add -set_exit_status=true when/if we want to enforce the linter rules + @golint -min_confidence 0.8 -set_exit_status $(GO_PKGS) + +vet: + @go vet $(GO_FLAGS) $(GO_PKGS) + +test: + @go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS) diff --git a/vendor/github.com/go-co-op/gocron/README.md b/vendor/github.com/go-co-op/gocron/README.md index dbe74d455..84a05ea54 100644 --- a/vendor/github.com/go-co-op/gocron/README.md +++ b/vendor/github.com/go-co-op/gocron/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,128 +13,71 @@ See also these two great articles: If you want to chat, you can find us at Slack! [](https://gophers.slack.com/archives/CQ7T0T1FW) -Examples: +## Concepts -```go -package main +- **Scheduler**: The scheduler tracks all the jobs assigned to it and makes sure they are passed to the executor when ready to be run. The scheduler is able to manage overall aspects of job behavior like limiting how many jobs are running at one time. +- **Job**: The job is simply aware of the task (go function) it's provided and is therefore only able to perform actions related to that task like preventing itself from overruning a previous task that is taking a long time. +- **Executor**: The executor, as it's name suggests, is simply responsible for calling the task (go function) that the job hands to it when sent by the scheduler. -import ( - "fmt" - "time" +## Examples - "github.com/go-co-op/gocron" -) +```golang +s := gocron.NewScheduler(time.UTC) -func task() { - fmt.Println("I am running task.") -} +s.Every(5).Seconds().Do(func(){ ... }) -func taskWithParams(a int, b string) { - fmt.Println(a, b) -} +// strings parse to duration +s.Every("5m").Do(func(){ ... }) -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") - - // Do jobs on specific weekday - s2.Every(1).Monday().Do(task) - s2.Every(1).Thursday().Do(task) +s.Every(5).Days().Do(func(){ ... }) +``` - // 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) +For more examples, take a look in our [go docs](https://pkg.go.dev/github.com/go-co-op/gocron#pkg-examples) - // Begin job at a specific date/time. - // Attention: scheduler timezone has precedence over job's timezone! - t := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC) - s2.Every(1).Hour().StartAt(t).Do(task) +## Options - // use .StartImmediately() to run job upon scheduler start - s2.Every(1).Hour().StartImmediately().Do(task) +Interval | Supported schedule options +-- | -- +sub-second | `StartAt()` +milliseconds | `StartAt()` +seconds | `StartAt()` +minutes | `StartAt()` +hours | `StartAt()` +days | `StartAt()`, `At()` +weeks | `StartAt()`, `At()`, `Weekday()` (and all week day named functions) +months | `StartAt()`, `At()` +There are several options available to restrict how jobs run: - // NextRun gets the next running time - _, time := s2.NextRun() - fmt.Println(time) +Mode | Function | Behavior +-- | -- | -- +Default | | jobs are rescheduled at every interval +Job singleton | `SingletonMode()` | a long running job will not be rescheduled until the current run is completed +Scheduler limit | `SetMaxConcurrentJobs()` | set a collective maximum number of concurrent jobs running across the scheduler - // Remove a specific job - s2.Remove(task) - // Clear all scheduled jobs - s2.Clear() +## FAQ - // stop our first scheduler (it still exists but doesn't run anymore) - s1.Stop() +* 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) - // executes the scheduler and blocks current thread - s2.StartBlocking() +* 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()`. - // this line is never reached -} -``` - -and full test cases and [document](http://godoc.org/github.com/jasonlvhit/gocron) will be coming soon (help is wanted! If you want to contribute, pull requests are welcome). +--- +Looking to contribute? Try to follow these guidelines: +* Use issues for everything +* For a small change, just send a PR! +* For bigger changes, please open an issue for discussion before sending a PR. +* PRs should have: tests, documentation and examples (if it makes sense) +* You can also contribute by: + * Reporting issues + * Suggesting new features or enhancements + * Improving/fixing documentation +--- -If you need to prevent a job from running at the same time from multiple cron instances (like running a cron app from multiple servers), -you can provide a [Locker implementation](example/lock.go) and lock the required jobs. +## Design -```go -gocron.SetLocker(lockerImplementation) -gocron.Every(1).Hour().Lock().Do(task) -``` +![design-diagram](https://user-images.githubusercontent.com/19351306/110375142-2ba88680-8017-11eb-80c3-554cc746b165.png) -Looking to contribute? Try to follow these guidelines: - * Use issues for everything - * For a small change, just send a PR! - * For bigger changes, please open an issue for discussion before sending a PR. - * PRs should have: tests, documentation and examples (if it makes sense) - * You can also contribute by: - * Reporting issues - * 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/vendor/github.com/go-co-op/gocron/executor.go b/vendor/github.com/go-co-op/gocron/executor.go new file mode 100644 index 000000000..077a90e29 --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/executor.go @@ -0,0 +1,101 @@ +package gocron + +import ( + "context" + "sync" + + "golang.org/x/sync/semaphore" +) + +const ( + // default is that if a limit on maximum concurrent jobs is set + // and the limit is reached, a job will skip it's run and try + // again on the next occurrence in the schedule + RescheduleMode limitMode = iota + + // in wait mode if a limit on maximum concurrent jobs is set + // and the limit is reached, a job will wait to try and run + // until a spot in the limit is freed up. + // + // Note: this mode can produce unpredictable results as + // job execution order isn't guaranteed. For example, a job that + // executes frequently may pile up in the wait queue and be executed + // many times back to back when the queue opens. + WaitMode +) + +type executor struct { + jobFunctions chan jobFunction + stop chan struct{} + limitMode limitMode + maxRunningJobs *semaphore.Weighted +} + +func newExecutor() executor { + return executor{ + jobFunctions: make(chan jobFunction, 1), + stop: make(chan struct{}, 1), + } +} + +func (e *executor) start() { + wg := sync.WaitGroup{} + stopCtx, cancel := context.WithCancel(context.Background()) + + for { + select { + case f := <-e.jobFunctions: + wg.Add(1) + go func() { + defer wg.Done() + + if e.maxRunningJobs != nil { + if !e.maxRunningJobs.TryAcquire(1) { + + switch e.limitMode { + case RescheduleMode: + return + case WaitMode: + for { + select { + case <-stopCtx.Done(): + return + case <-f.ctx.Done(): + return + default: + } + + if e.maxRunningJobs.TryAcquire(1) { + break + } + } + } + } + + defer e.maxRunningJobs.Release(1) + } + + switch f.runConfig.mode { + case defaultMode: + callJobFuncWithParams(f.functions[f.name], f.params[f.name]) + case singletonMode: + _, _, _ = f.limiter.Do("main", func() (interface{}, error) { + select { + case <-stopCtx.Done(): + return nil, nil + case <-f.ctx.Done(): + return nil, nil + default: + } + callJobFuncWithParams(f.functions[f.name], f.params[f.name]) + return nil, nil + }) + } + }() + case <-e.stop: + cancel() + wg.Wait() + return + } + } +} diff --git a/vendor/github.com/go-co-op/gocron/go.mod b/vendor/github.com/go-co-op/gocron/go.mod index 5baf982df..43c8197d7 100644 --- a/vendor/github.com/go-co-op/gocron/go.mod +++ b/vendor/github.com/go-co-op/gocron/go.mod @@ -1,12 +1,10 @@ module github.com/go-co-op/gocron -go 1.13 +go 1.16 require ( - github.com/go-redis/redis v6.15.5+incompatible - github.com/onsi/ginkgo v1.10.1 // indirect - github.com/onsi/gomega v1.7.0 // indirect - github.com/stretchr/testify v1.4.0 - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/stretchr/testify v1.7.0 + golang.org/x/sync v0.0.0-20201207232520-09787c993a3a + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/vendor/github.com/go-co-op/gocron/go.sum b/vendor/github.com/go-co-op/gocron/go.sum index 72fa7746f..c642744c2 100644 --- a/vendor/github.com/go-co-op/gocron/go.sum +++ b/vendor/github.com/go-co-op/gocron/go.sum @@ -1,44 +1,15 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-redis/redis v6.15.5+incompatible h1:pLky8I0rgiblWfa8C1EV7fPEUv0aH6vKRaYHc/YRHVk= -github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/go-co-op/gocron/gocron.go b/vendor/github.com/go-co-op/gocron/gocron.go index 04f6b5235..d81134090 100644 --- a/vendor/github.com/go-co-op/gocron/gocron.go +++ b/vendor/github.com/go-co-op/gocron/gocron.go @@ -1,17 +1,12 @@ // 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 ( - "crypto/sha256" "errors" "fmt" "reflect" @@ -22,15 +17,29 @@ import ( // Error declarations for gocron related errors var ( - ErrTimeFormat = errors.New("time format error") - ErrParamsNotAdapted = errors.New("the number of params is not adapted") - ErrNotAFunction = errors.New("only functions can be schedule into the job queue") - ErrPeriodNotSpecified = errors.New("unspecified job period") - ErrNotScheduledWeekday = errors.New("job not scheduled weekly on a weekday") - ErrJobNotFoundWithTag = errors.New("no jobs found with given tag") - ErrUnsupportedTimeFormat = errors.New("the given time format is not supported") + ErrNotAFunction = errors.New("only functions can be schedule into the job queue") + ErrNotScheduledWeekday = errors.New("job not scheduled weekly on a weekday") + ErrJobNotFoundWithTag = errors.New("no jobs found with given tag") + ErrUnsupportedTimeFormat = errors.New("the given time format is not supported") + ErrInvalidInterval = errors.New(".Every() interval must be greater than 0") + ErrInvalidIntervalType = errors.New(".Every() interval must be int, time.Duration, or string") + ErrInvalidIntervalUnitsSelection = errors.New("an .Every() duration interval cannot be used with units (e.g. .Seconds())") + + ErrAtTimeNotSupported = errors.New("the At() method is not supported for this time unit") + ErrWeekdayNotSupported = errors.New("weekday is not supported for time unit") + ErrTagsUnique = func(tag string) error { return fmt.Errorf("a non-unique tag was set on the job: %s", tag) } ) +func wrapOrError(toWrap error, err error) error { + var returnErr error + if toWrap != nil { + returnErr = fmt.Errorf("%s: %w", err, toWrap) + } else { + returnErr = err + } + return returnErr +} + // regex patterns for supported time formats var ( timeWithSeconds = regexp.MustCompile(`(?m)^\d{1,2}:\d\d:\d\d$`) @@ -40,36 +49,33 @@ var ( type timeUnit int const ( - seconds timeUnit = iota + 1 + // default unit is seconds + milliseconds timeUnit = iota + seconds minutes hours days weeks months + duration ) -func callJobFuncWithParams(jobFunc interface{}, params []interface{}) ([]reflect.Value, error) { +func callJobFuncWithParams(jobFunc interface{}, params []interface{}) { f := reflect.ValueOf(jobFunc) if len(params) != f.Type().NumIn() { - return nil, ErrParamsNotAdapted + return } in := make([]reflect.Value, len(params)) for k, param := range params { in[k] = reflect.ValueOf(param) } - return f.Call(in), nil + f.Call(in) } func getFunctionName(fn interface{}) string { return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() } -func getFunctionKey(funcName string) string { - h := sha256.New() - h.Write([]byte(funcName)) - return fmt.Sprintf("%x", h.Sum(nil)) -} - func parseTime(t string) (hour, min, sec int, err error) { var timeLayout string switch { diff --git a/vendor/github.com/go-co-op/gocron/job.go b/vendor/github.com/go-co-op/gocron/job.go index 9a9bca5cd..162b169b8 100644 --- a/vendor/github.com/go-co-op/gocron/job.go +++ b/vendor/github.com/go-co-op/gocron/job.go @@ -1,78 +1,130 @@ package gocron import ( + "context" "fmt" "sync" "time" + + "golang.org/x/sync/singleflight" ) // Job struct stores the information necessary to run a Job type Job struct { sync.RWMutex - interval uint64 // pause interval * unit between runs - unit timeUnit // time units, ,e.g. 'minutes', 'hours'... - startsImmediately bool // if the Job should run upon scheduler start - jobFunc string // the Job jobFunc to run, func[jobFunc] - atTime time.Duration // optional time at which this Job runs - err error // error related to Job - lastRun time.Time // datetime of last run - nextRun time.Time // datetime of next run - scheduledWeekday *time.Weekday // Specific day of the week to start on - dayOfTheMonth int // Specific day of the month to run the job - funcs map[string]interface{} // Map for the function task store - fparams map[string][]interface{} // Map for function and params of function - lock bool // lock the Job from running at same time form multiple instances - tags []string // allow the user to tag Jobs with certain labels - runConfig runConfig // configuration for how many times to run the job - runCount int // number of time the job ran + jobFunction + interval int // pause interval * unit between runs + duration time.Duration // time duration between runs + unit timeUnit // time units, ,e.g. 'minutes', 'hours'... + startsImmediately bool // if the Job should run upon scheduler start + atTime time.Duration // optional time at which this Job runs when interval is day + startAtTime time.Time // optional time at which the Job starts + error error // error related to Job + lastRun time.Time // datetime of last run + nextRun time.Time // datetime of next run + scheduledWeekday *time.Weekday // Specific day of the week to start on + dayOfTheMonth int // Specific day of the month to run the job + tags []string // allow the user to tag Jobs with certain labels + runCount int // number of times the job ran + timer *time.Timer +} + +type jobFunction struct { + functions map[string]interface{} // Map for the function task store + params map[string][]interface{} // Map for function and params of function + name string // the Job name to run, func[jobFunc] + runConfig runConfig // configuration for how many times to run the job + limiter *singleflight.Group // limits inflight runs of job to one + ctx context.Context // for cancellation + cancel context.CancelFunc // for cancellation } type runConfig struct { finiteRuns bool maxRuns int + mode mode } +// mode is the Job's running mode +type mode int8 + +const ( + // defaultMode disable any mode + defaultMode mode = iota + + // singletonMode switch to single job mode + singletonMode +) + // NewJob creates a new Job with the provided interval -func NewJob(interval uint64) *Job { +func NewJob(interval int) *Job { + ctx, cancel := context.WithCancel(context.Background()) return &Job{ interval: interval, + unit: seconds, lastRun: time.Time{}, nextRun: time.Time{}, - funcs: make(map[string]interface{}), - fparams: make(map[string][]interface{}), - tags: []string{}, + jobFunction: jobFunction{ + functions: make(map[string]interface{}), + params: make(map[string][]interface{}), + ctx: ctx, + cancel: cancel, + }, + tags: []string{}, + startsImmediately: true, } } -// Run the Job and immediately reschedule it -func (j *Job) run() { +func (j *Job) neverRan() bool { + return j.lastRun.IsZero() +} + +func (j *Job) getStartsImmediately() bool { + return j.startsImmediately +} + +func (j *Job) setStartsImmediately(b bool) { + j.startsImmediately = b +} + +func (j *Job) setTimer(t *time.Timer) { j.Lock() defer j.Unlock() - callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc]) - j.runCount++ + j.timer = t } -func (j *Job) neverRan() bool { - return j.lastRun.IsZero() +func (j *Job) getAtTime() time.Duration { + return j.atTime +} + +func (j *Job) setAtTime(t time.Duration) { + j.atTime = t } -// Err returns an error if one ocurred while creating the Job -func (j *Job) Err() error { - return j.err +func (j *Job) getStartAtTime() time.Time { + return j.startAtTime +} + +func (j *Job) setStartAtTime(t time.Time) { + j.startAtTime = t +} + +// Error returns an error if one occurred while creating the Job. +// If multiple errors occurred, they will be wrapped and can be +// checked using the standard unwrap options. +func (j *Job) Error() error { + return j.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) { - j.tags = append(j.tags, t) - for _, tag := range others { - j.tags = append(j.tags, tag) - } +func (j *Job) Tag(tags ...string) { + j.tags = append(j.tags, tags...) } // Untag removes a tag from a Job func (j *Job) Untag(t string) { - newTags := []string{} + var newTags []string for _, tag := range j.tags { if t != tag { newTags = append(newTags, tag) @@ -106,18 +158,73 @@ func (j *Job) Weekday() (time.Weekday, error) { return *j.scheduledWeekday, nil } -// LimitRunsTo limits the number of executions of this -// job to n. However, the job will still remain in the -// scheduler +// LimitRunsTo limits the number of executions of this job to n. +// The job will remain in the scheduler. +// Note: If a job is added to a running scheduler and this method is then used +// you may see the job run more than the set limit as job is scheduled immediately +// by default upon being added to the scheduler. It is recommended to use the +// LimitRunsTo() func on the scheduler chain when scheduling the job. +// For example: scheduler.LimitRunsTo(1).Do() func (j *Job) LimitRunsTo(n int) { - j.runConfig = runConfig{ - finiteRuns: true, - maxRuns: n, - } + j.Lock() + defer j.Unlock() + j.runConfig.finiteRuns = true + j.runConfig.maxRuns = n +} + +// SingletonMode prevents a new job from starting if the prior job has not yet +// completed it's run +// Note: If a job is added to a running scheduler and this method is then used +// you may see the job run overrun itself as job is scheduled immediately +// by default upon being added to the scheduler. It is recommended to use the +// SingletonMode() func on the scheduler chain when scheduling the job. +func (j *Job) SingletonMode() { + j.Lock() + defer j.Unlock() + j.runConfig.mode = singletonMode + j.jobFunction.limiter = &singleflight.Group{} + } -// shouldRun eveluates if this job should run again +// shouldRun evaluates if this job should run again // based on the runConfig func (j *Job) shouldRun() bool { + j.RLock() + defer j.RUnlock() return !j.runConfig.finiteRuns || j.runCount < j.runConfig.maxRuns } + +// LastRun returns the time the job was run last +func (j *Job) LastRun() time.Time { + return j.lastRun +} + +func (j *Job) setLastRun(t time.Time) { + j.lastRun = t +} + +// NextRun returns the time the job will run next +func (j *Job) NextRun() time.Time { + j.RLock() + defer j.RUnlock() + return j.nextRun +} + +func (j *Job) setNextRun(t time.Time) { + j.Lock() + defer j.Unlock() + j.nextRun = t +} + +// RunCount returns the number of time the job ran so far +func (j *Job) RunCount() int { + return j.runCount +} + +func (j *Job) stopTimer() { + j.Lock() + defer j.Unlock() + if j.timer != nil { + j.timer.Stop() + } +} diff --git a/vendor/github.com/go-co-op/gocron/locker.go b/vendor/github.com/go-co-op/gocron/locker.go deleted file mode 100644 index 338863b4e..000000000 --- a/vendor/github.com/go-co-op/gocron/locker.go +++ /dev/null @@ -1,19 +0,0 @@ -package gocron - -// Locker provides an interface for implementing job locking -// to prevent jobs from running at the same time on multiple -// instances of gocron -type Locker interface { - Lock(key string) (bool, error) - Unlock(key string) error -} - -var ( - locker Locker -) - -// SetLocker sets a locker implementation to be used by -// the scheduler for locking jobs -func SetLocker(l Locker) { - locker = l -} diff --git a/vendor/github.com/go-co-op/gocron/scheduler.go b/vendor/github.com/go-co-op/gocron/scheduler.go index eeefabc35..b8b6abab9 100644 --- a/vendor/github.com/go-co-op/gocron/scheduler.go +++ b/vendor/github.com/go-co-op/gocron/scheduler.go @@ -1,210 +1,298 @@ package gocron import ( - "fmt" "math" "reflect" "sort" "strings" + "sync" "time" + + "golang.org/x/sync/semaphore" ) +type limitMode int8 + // Scheduler struct stores a list of Jobs and the location of time Scheduler // Scheduler implements the sort.Interface{} for sorting Jobs, by the time of nextRun type Scheduler struct { - jobs []*Job - loc *time.Location + jobsMutex sync.RWMutex + jobs []*Job - running bool - stopChan chan struct{} // signal to stop scheduling + locationMutex sync.RWMutex + location *time.Location + runningMutex sync.RWMutex + running bool // represents if the scheduler is running at the moment or not - time timeWrapper // wrapper around time.Time + time timeWrapper // wrapper around time.Time + executor *executor // executes jobs passed via chan + + tags map[string]struct{} // for storing tags when unique tags is set } // NewScheduler creates a new Scheduler func NewScheduler(loc *time.Location) *Scheduler { + executor := newExecutor() + return &Scheduler{ jobs: make([]*Job, 0), - loc: loc, + location: loc, running: false, - stopChan: make(chan struct{}), time: &trueTime{}, + executor: &executor, } } -// StartBlocking starts all the pending jobs using a second-long ticker and blocks the current thread +// SetMaxConcurrentJobs limits how many jobs can be running at the same time. +// This is useful when running resource intensive jobs and a precise start time is not critical. +func (s *Scheduler) SetMaxConcurrentJobs(n int, mode limitMode) { + s.executor.maxRunningJobs = semaphore.NewWeighted(int64(n)) + s.executor.limitMode = mode +} + +// StartBlocking starts all jobs and blocks the current thread func (s *Scheduler) StartBlocking() { - <-s.StartAsync() -} - -// StartAsync starts a goroutine that runs all the pending using a second-long ticker -func (s *Scheduler) StartAsync() chan struct{} { - if s.running { - return s.stopChan - } - s.running = true - - s.scheduleAllJobs() - ticker := s.time.NewTicker(1 * time.Second) - go func() { - for { - select { - case <-ticker.C: - s.RunPending() - case <-s.stopChan: - ticker.Stop() - s.running = false - return - } - } - }() + s.StartAsync() + <-make(chan bool) +} + +// StartAsync starts all jobs without blocking the current thread +func (s *Scheduler) StartAsync() { + if !s.IsRunning() { + s.start() + } +} + +//start starts the scheduler, scheduling and running jobs +func (s *Scheduler) start() { + go s.executor.start() + s.setRunning(true) + s.runJobs(s.Jobs()) +} + +func (s *Scheduler) runJobs(jobs []*Job) { + for _, job := range jobs { + s.scheduleNextRun(job) + } +} + +func (s *Scheduler) setRunning(b bool) { + s.runningMutex.Lock() + defer s.runningMutex.Unlock() + s.running = b +} - return s.stopChan +// IsRunning returns true if the scheduler is running +func (s *Scheduler) IsRunning() bool { + s.runningMutex.RLock() + defer s.runningMutex.RUnlock() + return s.running } // Jobs returns the list of Jobs from the Scheduler func (s *Scheduler) Jobs() []*Job { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() return s.jobs } -// Len returns the number of Jobs in the Scheduler +func (s *Scheduler) setJobs(jobs []*Job) { + s.jobsMutex.Lock() + defer s.jobsMutex.Unlock() + s.jobs = jobs +} + +// Len returns the number of Jobs in the Scheduler - implemented for sort func (s *Scheduler) Len() int { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() return len(s.jobs) } -// Swap -func (s *Scheduler) Swap(i, j int) { - s.jobs[i], s.jobs[j] = s.jobs[j], s.jobs[i] +// Swap places each job into the other job's position given +// the provided job indexes. +func (s *Scheduler) Swap(i, job int) { + s.jobsMutex.Lock() + defer s.jobsMutex.Unlock() + s.jobs[i], s.jobs[job] = s.jobs[job], 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 func (s *Scheduler) ChangeLocation(newLocation *time.Location) { - s.loc = newLocation + s.locationMutex.Lock() + defer s.locationMutex.Unlock() + s.location = newLocation +} + +// Location provides the current location set on the scheduler +func (s *Scheduler) Location() *time.Location { + s.locationMutex.RLock() + defer s.locationMutex.RUnlock() + return s.location } // scheduleNextRun Compute the instant when this Job should run next -func (s *Scheduler) scheduleNextRun(j *Job) { - j.Lock() - defer j.Unlock() - now := s.time.Now(s.loc) - - if j.startsImmediately { - j.nextRun = now - j.startsImmediately = false +func (s *Scheduler) scheduleNextRun(job *Job) { + now := s.now() + lastRun := job.LastRun() + + if !s.jobPresent(job) { return } - // delta represent the time slice used to calculate the next run - // it can be the last time ran by a job, or time.Now() if the job never ran - var delta time.Time - if j.neverRan() { - if !j.nextRun.IsZero() { // scheduled for future run, wait to run at least once - return + if job.getStartsImmediately() { + s.run(job) + job.setStartsImmediately(false) + } + + if job.neverRan() { + // Increment startAtTime until it is in the future + for job.startAtTime.Before(now) && !job.startAtTime.IsZero() { + job.startAtTime = job.startAtTime.Add(s.durationToNextRun(job.startAtTime, job)) } - delta = now - } else { - delta = j.lastRun + lastRun = now + } + + if !job.shouldRun() { + s.RemoveByReference(job) + return } - switch j.unit { - case seconds, minutes, hours: - j.nextRun = s.rescheduleDuration(j, delta) + durationToNextRun := s.durationToNextRun(lastRun, job) + job.setNextRun(lastRun.Add(durationToNextRun)) + job.setTimer(time.AfterFunc(durationToNextRun, func() { + s.run(job) + s.scheduleNextRun(job) + })) +} + +func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) time.Duration { + // job can be scheduled with .StartAt() + if job.getStartAtTime().After(lastRun) { + return job.getStartAtTime().Sub(s.now()) + } + + var d time.Duration + switch job.unit { + case milliseconds, seconds, minutes, hours: + d = s.calculateDuration(job) case days: - j.nextRun = s.rescheduleDay(j, delta) + d = s.calculateDays(job, lastRun) case weeks: - j.nextRun = s.rescheduleWeek(j, delta) + if job.scheduledWeekday != nil { // weekday selected, Every().Monday(), for example + d = s.calculateWeekday(job, lastRun) + } else { + d = s.calculateWeeks(job, lastRun) + } case months: - j.nextRun = s.rescheduleMonth(j, delta) + d = s.calculateMonths(job, lastRun) + case duration: + d = job.duration } + return d } -func (s *Scheduler) rescheduleMonth(j *Job, delta time.Time) time.Time { - if j.neverRan() { // calculate days to j.dayOfTheMonth - jobDay := time.Date(delta.Year(), delta.Month(), j.dayOfTheMonth, 0, 0, 0, 0, s.loc).Add(j.atTime) - daysDifference := int(math.Abs(delta.Sub(jobDay).Hours()) / 24) - nextRun := s.roundToMidnight(delta) - if jobDay.Before(delta) { // shouldn't run this month; schedule for next interval minus day difference +func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) time.Duration { + lastRunRoundedMidnight := s.roundToMidnight(lastRun) - nextRun = nextRun.AddDate(0, int(j.interval), -daysDifference) + if job.dayOfTheMonth > 0 { // calculate days to job.dayOfTheMonth + jobDay := time.Date(lastRun.Year(), lastRun.Month(), job.dayOfTheMonth, 0, 0, 0, 0, s.Location()).Add(job.getAtTime()) + daysDifference := int(math.Abs(lastRun.Sub(jobDay).Hours()) / 24) + nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime()) + if jobDay.Before(lastRun) { // shouldn't run this month; schedule for next interval minus day difference + nextRun = nextRun.AddDate(0, int(job.interval), -daysDifference) } else { - if j.interval == 1 { // every month counts current month - nextRun = nextRun.AddDate(0, int(j.interval)-1, daysDifference) + if job.interval == 1 { // every month counts current month + nextRun = nextRun.AddDate(0, int(job.interval)-1, daysDifference) } else { // should run next month interval - nextRun = nextRun.AddDate(0, int(j.interval), daysDifference) + nextRun = nextRun.AddDate(0, int(job.interval), daysDifference) } } - return nextRun.Add(j.atTime) + return s.until(lastRun, nextRun) } - return s.roundToMidnight(delta).AddDate(0, int(j.interval), 0).Add(j.atTime) + nextRun := lastRunRoundedMidnight.Add(job.getAtTime()).AddDate(0, int(job.interval), 0) + return s.until(lastRunRoundedMidnight, nextRun) } -func (s *Scheduler) rescheduleWeek(j *Job, delta time.Time) time.Time { - var days int - if j.scheduledWeekday != nil { // weekday selected, Every().Monday(), for example - days = s.calculateWeekdayDifference(delta, j) - } else { - days = int(j.interval) * 7 - } - delta = s.roundToMidnight(delta) - return delta.AddDate(0, 0, days).Add(j.atTime) +func (s *Scheduler) calculateWeekday(job *Job, lastRun time.Time) time.Duration { + daysToWeekday := remainingDaysToWeekday(lastRun.Weekday(), *job.scheduledWeekday) + totalDaysDifference := s.calculateTotalDaysDifference(lastRun, daysToWeekday, job) + nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, totalDaysDifference) + return s.until(lastRun, nextRun) } -func (s *Scheduler) rescheduleDay(j *Job, delta time.Time) time.Time { - if j.interval == 1 { - atTime := time.Date(delta.Year(), delta.Month(), delta.Day(), 0, 0, 0, 0, s.loc).Add(j.atTime) - if delta.Before(atTime) { // should run today - return s.roundToMidnight(delta).Add(j.atTime) +func (s *Scheduler) calculateWeeks(job *Job, lastRun time.Time) time.Duration { + totalDaysDifference := int(job.interval) * 7 + nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, totalDaysDifference) + return s.until(lastRun, nextRun) +} + +func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekday int, job *Job) int { + if job.interval > 1 { // every N weeks counts rest of this week and full N-1 weeks + return daysToWeekday + int(job.interval-1)*7 + } + + if daysToWeekday == 0 { // today, at future time or already passed + lastRunAtTime := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, s.Location()).Add(job.getAtTime()) + if lastRun.Before(lastRunAtTime) || lastRun.Equal(lastRunAtTime) { + return 0 } + return 7 } - return s.roundToMidnight(delta).AddDate(0, 0, int(j.interval)).Add(j.atTime) + + return daysToWeekday } -func (s *Scheduler) rescheduleDuration(j *Job, delta time.Time) time.Time { - if j.neverRan() && j.atTime != 0 { // ugly. in order to avoid this we could prohibit setting .At() and allowing only .StartAt() when dealing with Duration types - atTime := time.Date(delta.Year(), delta.Month(), delta.Day(), 0, 0, 0, 0, s.loc).Add(j.atTime) - if delta.Before(atTime) || delta.Equal(atTime) { - return s.roundToMidnight(delta).Add(j.atTime) +func (s *Scheduler) calculateDays(job *Job, lastRun time.Time) time.Duration { + if job.interval == 1 { + lastRunDayPlusJobAtTime := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, s.Location()).Add(job.getAtTime()) + if shouldRunToday(lastRun, lastRunDayPlusJobAtTime) { + return s.until(lastRun, s.roundToMidnight(lastRun).Add(job.getAtTime())) } } - var periodDuration time.Duration - switch j.unit { - case seconds: - periodDuration = time.Duration(j.interval) * time.Second - case minutes: - periodDuration = time.Duration(j.interval) * time.Minute - case hours: - periodDuration = time.Duration(j.interval) * time.Hour - } - return delta.Add(periodDuration) + nextRunAtTime := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, int(job.interval)).In(s.Location()) + return s.until(lastRun, nextRunAtTime) } -func (s *Scheduler) calculateWeekdayDifference(delta time.Time, j *Job) int { - daysToWeekday := remainingDaysToWeekday(delta.Weekday(), *j.scheduledWeekday) - - if j.interval > 1 { - return daysToWeekday + int(j.interval-1)*7 // minus a week since to compensate daysToWeekday - } +func (s *Scheduler) until(from time.Time, until time.Time) time.Duration { + return until.Sub(from) +} - if daysToWeekday > 0 { // within the next following days, but not today - return daysToWeekday - } +func shouldRunToday(lastRun time.Time, atTime time.Time) bool { + return lastRun.Before(atTime) +} - // following paths are on same day - if j.atTime.Seconds() == 0 && j.neverRan() { // .At() not set, run today - return 0 +func (s *Scheduler) calculateDuration(job *Job) time.Duration { + lastRun := job.LastRun() + if job.neverRan() && shouldRunAtSpecificTime(job) { // ugly. in order to avoid this we could prohibit setting .At() and allowing only .StartAt() when dealing with Duration types + atTime := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, s.Location()).Add(job.getAtTime()) + if lastRun.Before(atTime) || lastRun.Equal(atTime) { + return time.Until(s.roundToMidnight(lastRun).Add(job.getAtTime())) + } } - atJobTime := time.Date(delta.Year(), delta.Month(), delta.Day(), 0, 0, 0, 0, s.loc).Add(j.atTime) - if delta.Before(atJobTime) || delta.Equal(atJobTime) { // .At() set and should run today - return 0 + interval := job.interval + switch job.unit { + case milliseconds: + return time.Duration(interval) * time.Millisecond + case seconds: + return time.Duration(interval) * time.Second + case minutes: + return time.Duration(interval) * time.Minute + default: + return time.Duration(interval) * time.Hour } +} - return 7 +func shouldRunAtSpecificTime(job *Job) bool { + return job.getAtTime() != 0 } func remainingDaysToWeekday(from time.Weekday, to time.Weekday) int { @@ -217,69 +305,64 @@ func remainingDaysToWeekday(from time.Weekday, to time.Weekday) int { // roundToMidnight truncates time to midnight func (s *Scheduler) roundToMidnight(t time.Time) time.Time { - return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, s.loc) -} - -// Get the current runnable Jobs, which shouldRun is True -func (s *Scheduler) runnableJobs() []*Job { - var runnableJobs []*Job - sort.Sort(s) - for _, job := range s.jobs { - if s.shouldRun(job) { - runnableJobs = append(runnableJobs, job) - } else { - break - } - } - return runnableJobs + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, s.Location()) } // NextRun datetime when the next Job should run. func (s *Scheduler) NextRun() (*Job, time.Time) { - if len(s.jobs) <= 0 { - return nil, s.time.Now(s.loc) + if len(s.Jobs()) <= 0 { + return nil, s.now() } - sort.Sort(s) - return s.jobs[0], s.jobs[0].nextRun -} -// Every schedules a new periodic Job with interval -func (s *Scheduler) Every(interval uint64) *Scheduler { - job := NewJob(interval) - s.jobs = append(s.jobs, job) - return s -} + sort.Sort(s) -// RunPending runs all the Jobs that are scheduled to run. -func (s *Scheduler) RunPending() { - for _, job := range s.runnableJobs() { - s.runAndReschedule(job) // we should handle this error somehow - } + return s.Jobs()[0], s.Jobs()[0].NextRun() } -func (s *Scheduler) runAndReschedule(job *Job) error { - if err := s.run(job); err != nil { - return err +// 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: + job := NewJob(interval) + if interval <= 0 { + job.error = wrapOrError(job.error, ErrInvalidInterval) + } + s.setJobs(append(s.Jobs(), job)) + case time.Duration: + job := NewJob(0) + job.duration = interval + job.unit = duration + s.setJobs(append(s.Jobs(), job)) + case string: + job := NewJob(0) + d, err := time.ParseDuration(interval) + if err != nil { + job.error = wrapOrError(job.error, err) + } + job.duration = d + job.unit = duration + s.setJobs(append(s.Jobs(), job)) + default: + job := NewJob(0) + job.error = wrapOrError(job.error, ErrInvalidIntervalType) + s.setJobs(append(s.Jobs(), job)) } - s.scheduleNextRun(job) - return nil + return s } -func (s *Scheduler) run(job *Job) error { - if job.lock { - if locker == nil { - return fmt.Errorf("trying to lock %s with nil locker", job.jobFunc) - } - key := getFunctionKey(job.jobFunc) - - locker.Lock(key) - defer locker.Unlock(key) +func (s *Scheduler) run(job *Job) { + if !s.IsRunning() { + return } - job.lastRun = s.time.Now(s.loc) - go job.run() - - return nil + job.Lock() + defer job.Unlock() + job.setLastRun(s.now()) + job.runCount++ + s.executor.jobFunctions <- job.jobFunction } // RunAll run all Jobs regardless if they are scheduled to run or not @@ -287,55 +370,65 @@ func (s *Scheduler) RunAll() { s.RunAllWithDelay(0) } -// RunAllWithDelay runs all Jobs with delay seconds -func (s *Scheduler) RunAllWithDelay(d int) { - for _, job := range s.jobs { - err := s.run(job) - if err != nil { - continue - } - s.time.Sleep(time.Duration(d) * time.Second) +// 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(d) } } -// Remove specific Job j by function -func (s *Scheduler) Remove(j interface{}) { +// Remove specific Job job 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(job interface{}) { s.removeByCondition(func(someJob *Job) bool { - return someJob.jobFunc == getFunctionName(j) + return someJob.name == getFunctionName(job) }) } -// RemoveByReference removes specific Job j by reference -func (s *Scheduler) RemoveByReference(j *Job) { +// RemoveByReference removes specific Job job by reference +func (s *Scheduler) RemoveByReference(job *Job) { s.removeByCondition(func(someJob *Job) bool { - return someJob == j + job.RLock() + defer job.RUnlock() + return someJob == job }) } func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) { retainedJobs := make([]*Job, 0) - for _, job := range s.jobs { + for _, job := range s.Jobs() { if !shouldRemove(job) { retainedJobs = append(retainedJobs, job) + } else { + job.stopTimer() + job.cancel() } } - s.jobs = retainedJobs + s.setJobs(retainedJobs) } -// RemoveJobByTag will Remove Jobs by Tag -func (s *Scheduler) RemoveJobByTag(tag string) error { - jobindex, err := s.findJobsIndexByTag(tag) +// RemoveByTag will remove a job by a given tag. +func (s *Scheduler) RemoveByTag(tag string) error { + index, err := s.findJobsIndexByTag(tag) if err != nil { return err } - // Remove job if jobindex is valid - s.jobs = removeAtIndex(s.jobs, jobindex) + // Remove job if job index is valid + s.jobs[index].stopTimer() + s.jobs[index].cancel() + s.setJobs(removeAtIndex(s.jobs, index)) return nil } // Find first job index by given string func (s *Scheduler) findJobsIndexByTag(tag string) (int, error) { - for i, job := range s.jobs { + for i, job := range s.Jobs() { if strings.Contains(strings.Join(job.Tags(), " "), tag) { return i, nil } @@ -351,10 +444,39 @@ func removeAtIndex(jobs []*Job, i int) []*Job { return jobs } -// Scheduled checks if specific Job j was already added -func (s *Scheduler) Scheduled(j interface{}) bool { - for _, job := range s.jobs { - if job.jobFunc == getFunctionName(j) { +// LimitRunsTo limits the number of executions of this job to n. +// Upon reaching the limit, the job is removed from the scheduler. +func (s *Scheduler) LimitRunsTo(i int) *Scheduler { + job := s.getCurrentJob() + job.LimitRunsTo(i) + return s +} + +// SingletonMode prevents a new job from starting if the prior job has not yet +// completed it's run +func (s *Scheduler) SingletonMode() *Scheduler { + job := s.getCurrentJob() + job.SingletonMode() + return s +} + +// TaskPresent checks if specific job's function was added to the scheduler. +func (s *Scheduler) TaskPresent(j interface{}) bool { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.Jobs() { + if job.name == getFunctionName(j) { + return true + } + } + return false +} + +func (s *Scheduler) jobPresent(j *Job) bool { + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + for _, job := range s.Jobs() { + if job == j { return true } } @@ -363,87 +485,131 @@ func (s *Scheduler) Scheduled(j interface{}) bool { // Clear clear all Jobs from this scheduler func (s *Scheduler) Clear() { - s.jobs = make([]*Job, 0) + for _, j := range s.Jobs() { + j.stopTimer() + } + s.setJobs(make([]*Job, 0)) } // Stop stops the scheduler. This is a no-op if the scheduler is already stopped . func (s *Scheduler) Stop() { - if s.running { - s.stopScheduler() + if s.IsRunning() { + s.stop() } } -func (s *Scheduler) stopScheduler() { - s.stopChan <- struct{}{} +func (s *Scheduler) stop() { + s.setRunning(false) + s.executor.stop <- struct{}{} } // Do specifies the jobFunc that should be called every time the Job runs func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) { - j := s.getCurrentJob() - if j.err != nil { - return nil, j.err + job := s.getCurrentJob() + + if job.atTime != 0 && job.unit <= hours { + job.error = wrapOrError(job.error, ErrAtTimeNotSupported) + } + + if job.scheduledWeekday != nil && job.unit != weeks { + job.error = wrapOrError(job.error, ErrWeekdayNotSupported) + } + + if job.error != nil { + // delete the job from the scheduler as this job + // cannot be executed + s.RemoveByReference(job) + return nil, job.error } typ := reflect.TypeOf(jobFun) if typ.Kind() != reflect.Func { + // delete the job for the same reason as above + s.RemoveByReference(job) return nil, ErrNotAFunction } fname := getFunctionName(jobFun) - j.funcs[fname] = jobFun - j.fparams[fname] = params - j.jobFunc = fname + job.functions[fname] = jobFun + job.params[fname] = params + job.name = fname // we should not schedule if not running since we cant foresee how long it will take for the scheduler to start - if s.running { - s.scheduleNextRun(j) + if s.IsRunning() { + s.scheduleNextRun(job) } - return j, nil + return job, nil } // At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM" -func (s *Scheduler) At(t string) *Scheduler { - j := s.getCurrentJob() - hour, min, sec, err := parseTime(t) - if err != nil { - j.err = ErrTimeFormat - return s - } - // save atTime start as duration from midnight - j.atTime = time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second +// or time.Time (note that only the hours, minutes, seconds and nanos are used). +func (s *Scheduler) At(i interface{}) *Scheduler { + job := s.getCurrentJob() + + switch t := i.(type) { + case string: + hour, min, sec, err := parseTime(t) + if err != nil { + job.error = wrapOrError(job.error, err) + return s + } + // save atTime start as duration from midnight + job.setAtTime(time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second) + case time.Time: + job.setAtTime(time.Duration(t.Hour())*time.Hour + time.Duration(t.Minute())*time.Minute + time.Duration(t.Second())*time.Second + time.Duration(t.Nanosecond())*time.Nanosecond) + default: + job.error = wrapOrError(job.error, ErrUnsupportedTimeFormat) + } + job.startsImmediately = false 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() + + if s.tags != nil { + for _, tag := range t { + if _, ok := s.tags[tag]; ok { + job.error = wrapOrError(job.error, ErrTagsUnique(tag)) + return s + } + s.tags[tag] = struct{}{} + } + } + job.tags = t return s } -// StartAt schedules the next run of the Job +// StartAt schedules the next run of the Job. If this time is in the past, the configured interval will be used +// to calculate the next future time func (s *Scheduler) StartAt(t time.Time) *Scheduler { - s.getCurrentJob().nextRun = t + job := s.getCurrentJob() + job.setStartAtTime(t) + job.startsImmediately = false return s } -// StartImmediately sets the Jobs next run as soon as the scheduler starts -func (s *Scheduler) StartImmediately() *Scheduler { +// setUnit sets the unit type +func (s *Scheduler) setUnit(unit timeUnit) { job := s.getCurrentJob() - job.startsImmediately = true - return s + if job.unit == duration { + job.error = wrapOrError(job.error, ErrInvalidIntervalUnitsSelection) + } + job.unit = unit } -// shouldRun returns true if the Job should be run now -func (s *Scheduler) shouldRun(j *Job) bool { - return j.shouldRun() && s.time.Now(s.loc).Unix() >= j.nextRun.Unix() +// Second sets the unit with seconds +func (s *Scheduler) Millisecond() *Scheduler { + return s.Milliseconds() } -// setUnit sets the unit type -func (s *Scheduler) setUnit(unit timeUnit) { - currentJob := s.getCurrentJob() - currentJob.unit = unit +// Seconds sets the unit with seconds +func (s *Scheduler) Milliseconds() *Scheduler { + s.setUnit(milliseconds) + return s } // Second sets the unit with seconds @@ -510,7 +676,9 @@ func (s *Scheduler) Month(dayOfTheMonth int) *Scheduler { // Months sets the unit with months func (s *Scheduler) Months(dayOfTheMonth int) *Scheduler { - s.getCurrentJob().dayOfTheMonth = dayOfTheMonth + job := s.getCurrentJob() + job.dayOfTheMonth = dayOfTheMonth + job.startsImmediately = false s.setUnit(months) return s } @@ -522,7 +690,9 @@ func (s *Scheduler) Months(dayOfTheMonth int) *Scheduler { // Weekday sets the start with a specific weekday weekday func (s *Scheduler) Weekday(startDay time.Weekday) *Scheduler { - s.getCurrentJob().scheduledWeekday = &startDay + job := s.getCurrentJob() + job.scheduledWeekday = &startDay + job.startsImmediately = false s.setUnit(weeks) return s } @@ -563,17 +733,17 @@ func (s *Scheduler) Sunday() *Scheduler { } func (s *Scheduler) getCurrentJob() *Job { - return s.jobs[len(s.jobs)-1] + return s.Jobs()[len(s.jobs)-1] } -// Lock prevents Job to run from multiple instances of gocron -func (s *Scheduler) Lock() *Scheduler { - s.getCurrentJob().lock = true - return s +func (s *Scheduler) now() time.Time { + return s.time.Now(s.Location()) } -func (s *Scheduler) scheduleAllJobs() { - for _, j := range s.jobs { - s.scheduleNextRun(j) - } +// TagsUnique forces job tags to be unique across the scheduler +// when adding tags with (s *Scheduler) Tag(). +// This does not enforce uniqueness on tags added via +// (j *Job) Tag() +func (s *Scheduler) TagsUnique() { + s.tags = make(map[string]struct{}) } diff --git a/vendor/github.com/go-co-op/gocron/timeHelper.go b/vendor/github.com/go-co-op/gocron/timeHelper.go index ef5d45ab0..b5baeb573 100644 --- a/vendor/github.com/go-co-op/gocron/timeHelper.go +++ b/vendor/github.com/go-co-op/gocron/timeHelper.go @@ -2,11 +2,12 @@ package gocron import "time" +var _ timeWrapper = (*trueTime)(nil) + type timeWrapper interface { Now(*time.Location) time.Time Unix(int64, int64) time.Time Sleep(time.Duration) - NewTicker(time.Duration) *time.Ticker } type trueTime struct{} @@ -22,7 +23,3 @@ func (t *trueTime) Unix(sec int64, nsec int64) time.Time { func (t *trueTime) Sleep(d time.Duration) { time.Sleep(d) } - -func (t *trueTime) NewTicker(d time.Duration) *time.Ticker { - return time.NewTicker(d) -} diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 000000000..15167cd74 --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 000000000..1c4577e96 --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +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 +OWNER 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. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go new file mode 100644 index 000000000..30f632c57 --- /dev/null +++ b/vendor/golang.org/x/sync/semaphore/semaphore.go @@ -0,0 +1,136 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semaphore provides a weighted semaphore implementation. +package semaphore // import "golang.org/x/sync/semaphore" + +import ( + "container/list" + "context" + "sync" +) + +type waiter struct { + n int64 + ready chan<- struct{} // Closed when semaphore acquired. +} + +// NewWeighted creates a new weighted semaphore with the given +// maximum combined weight for concurrent access. +func NewWeighted(n int64) *Weighted { + w := &Weighted{size: n} + return w +} + +// Weighted provides a way to bound concurrent access to a resource. +// The callers can request access with a given weight. +type Weighted struct { + size int64 + cur int64 + mu sync.Mutex + waiters list.List +} + +// Acquire acquires the semaphore with a weight of n, blocking until resources +// are available or ctx is done. On success, returns nil. On failure, returns +// ctx.Err() and leaves the semaphore unchanged. +// +// If ctx is already done, Acquire may still succeed without blocking. +func (s *Weighted) Acquire(ctx context.Context, n int64) error { + s.mu.Lock() + if s.size-s.cur >= n && s.waiters.Len() == 0 { + s.cur += n + s.mu.Unlock() + return nil + } + + if n > s.size { + // Don't make other Acquire calls block on one that's doomed to fail. + s.mu.Unlock() + <-ctx.Done() + return ctx.Err() + } + + ready := make(chan struct{}) + w := waiter{n: n, ready: ready} + elem := s.waiters.PushBack(w) + s.mu.Unlock() + + select { + case <-ctx.Done(): + err := ctx.Err() + s.mu.Lock() + select { + case <-ready: + // Acquired the semaphore after we were canceled. Rather than trying to + // fix up the queue, just pretend we didn't notice the cancelation. + err = nil + default: + isFront := s.waiters.Front() == elem + s.waiters.Remove(elem) + // If we're at the front and there're extra tokens left, notify other waiters. + if isFront && s.size > s.cur { + s.notifyWaiters() + } + } + s.mu.Unlock() + return err + + case <-ready: + return nil + } +} + +// TryAcquire acquires the semaphore with a weight of n without blocking. +// On success, returns true. On failure, returns false and leaves the semaphore unchanged. +func (s *Weighted) TryAcquire(n int64) bool { + s.mu.Lock() + success := s.size-s.cur >= n && s.waiters.Len() == 0 + if success { + s.cur += n + } + s.mu.Unlock() + return success +} + +// Release releases the semaphore with a weight of n. +func (s *Weighted) Release(n int64) { + s.mu.Lock() + s.cur -= n + if s.cur < 0 { + s.mu.Unlock() + panic("semaphore: released more than held") + } + s.notifyWaiters() + s.mu.Unlock() +} + +func (s *Weighted) notifyWaiters() { + for { + next := s.waiters.Front() + if next == nil { + break // No more waiters blocked. + } + + w := next.Value.(waiter) + if s.size-s.cur < w.n { + // Not enough tokens for the next waiter. We could keep going (to try to + // find a waiter with a smaller request), but under load that could cause + // starvation for large requests; instead, we leave all remaining waiters + // blocked. + // + // Consider a semaphore used as a read-write lock, with N tokens, N + // readers, and one writer. Each reader can Acquire(1) to obtain a read + // lock. The writer can Acquire(N) to obtain a write lock, excluding all + // of the readers. If we allow the readers to jump ahead in the queue, + // the writer will starve — there is always one token available for every + // reader. + break + } + + s.cur += w.n + s.waiters.Remove(next) + close(w.ready) + } +} diff --git a/vendor/golang.org/x/sync/singleflight/singleflight.go b/vendor/golang.org/x/sync/singleflight/singleflight.go new file mode 100644 index 000000000..690eb8501 --- /dev/null +++ b/vendor/golang.org/x/sync/singleflight/singleflight.go @@ -0,0 +1,212 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package singleflight provides a duplicate function call suppression +// mechanism. +package singleflight // import "golang.org/x/sync/singleflight" + +import ( + "bytes" + "errors" + "fmt" + "runtime" + "runtime/debug" + "sync" +) + +// errGoexit indicates the runtime.Goexit was called in +// the user given function. +var errGoexit = errors.New("runtime.Goexit was called") + +// A panicError is an arbitrary value recovered from a panic +// with the stack trace during the execution of given function. +type panicError struct { + value interface{} + stack []byte +} + +// Error implements error interface. +func (p *panicError) Error() string { + return fmt.Sprintf("%v\n\n%s", p.value, p.stack) +} + +func newPanicError(v interface{}) error { + stack := debug.Stack() + + // The first line of the stack trace is of the form "goroutine N [status]:" + // but by the time the panic reaches Do the goroutine may no longer exist + // and its status will have changed. Trim out the misleading line. + if line := bytes.IndexByte(stack[:], '\n'); line >= 0 { + stack = stack[line+1:] + } + return &panicError{value: v, stack: stack} +} + +// call is an in-flight or completed singleflight.Do call +type call struct { + wg sync.WaitGroup + + // These fields are written once before the WaitGroup is done + // and are only read after the WaitGroup is done. + val interface{} + err error + + // forgotten indicates whether Forget was called with this call's key + // while the call was still in flight. + forgotten bool + + // These fields are read and written with the singleflight + // mutex held before the WaitGroup is done, and are read but + // not written after the WaitGroup is done. + dups int + chans []chan<- Result +} + +// Group represents a class of work and forms a namespace in +// which units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Result holds the results of Do, so they can be passed +// on a channel. +type Result struct { + Val interface{} + Err error + Shared bool +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +// The return value shared indicates whether v was given to multiple callers. +func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.mu.Unlock() + c.wg.Wait() + + if e, ok := c.err.(*panicError); ok { + panic(e) + } else if c.err == errGoexit { + runtime.Goexit() + } + return c.val, c.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + g.doCall(c, key, fn) + return c.val, c.err, c.dups > 0 +} + +// DoChan is like Do but returns a channel that will receive the +// results when they are ready. +// +// The returned channel will not be closed. +func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result { + ch := make(chan Result, 1) + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + c.chans = append(c.chans, ch) + g.mu.Unlock() + return ch + } + c := &call{chans: []chan<- Result{ch}} + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + go g.doCall(c, key, fn) + + return ch +} + +// doCall handles the single call for a key. +func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) { + normalReturn := false + recovered := false + + // use double-defer to distinguish panic from runtime.Goexit, + // more details see https://golang.org/cl/134395 + defer func() { + // the given function invoked runtime.Goexit + if !normalReturn && !recovered { + c.err = errGoexit + } + + c.wg.Done() + g.mu.Lock() + defer g.mu.Unlock() + if !c.forgotten { + delete(g.m, key) + } + + if e, ok := c.err.(*panicError); ok { + // In order to prevent the waiting channels from being blocked forever, + // needs to ensure that this panic cannot be recovered. + if len(c.chans) > 0 { + go panic(e) + select {} // Keep this goroutine around so that it will appear in the crash dump. + } else { + panic(e) + } + } else if c.err == errGoexit { + // Already in the process of goexit, no need to call again + } else { + // Normal return + for _, ch := range c.chans { + ch <- Result{c.val, c.err, c.dups > 0} + } + } + }() + + func() { + defer func() { + if !normalReturn { + // Ideally, we would wait to take a stack trace until we've determined + // whether this is a panic or a runtime.Goexit. + // + // Unfortunately, the only way we can distinguish the two is to see + // whether the recover stopped the goroutine from terminating, and by + // the time we know that, the part of the stack trace relevant to the + // panic has been discarded. + if r := recover(); r != nil { + c.err = newPanicError(r) + } + } + }() + + c.val, c.err = fn() + normalReturn = true + }() + + if !normalReturn { + recovered = true + } +} + +// Forget tells the singleflight to forget about a key. Future calls +// to Do for this key will call the function rather than waiting for +// an earlier call to complete. +func (g *Group) Forget(key string) { + g.mu.Lock() + if c, ok := g.m[key]; ok { + c.forgotten = true + } + delete(g.m, key) + g.mu.Unlock() +} diff --git a/vendor/gopkg.in/yaml.v3/.travis.yml b/vendor/gopkg.in/yaml.v3/.travis.yml deleted file mode 100644 index 04d4dae09..000000000 --- a/vendor/gopkg.in/yaml.v3/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go - -go: - - "1.4.x" - - "1.5.x" - - "1.6.x" - - "1.7.x" - - "1.8.x" - - "1.9.x" - - "1.10.x" - - "1.11.x" - - "1.12.x" - - "1.13.x" - - "tip" - -go_import_path: gopkg.in/yaml.v3 diff --git a/vendor/gopkg.in/yaml.v3/apic.go b/vendor/gopkg.in/yaml.v3/apic.go index 65846e674..ae7d049f1 100644 --- a/vendor/gopkg.in/yaml.v3/apic.go +++ b/vendor/gopkg.in/yaml.v3/apic.go @@ -108,6 +108,7 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) { raw_buffer: make([]byte, 0, output_raw_buffer_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size), events: make([]yaml_event_t, 0, initial_queue_size), + best_width: -1, } } diff --git a/vendor/gopkg.in/yaml.v3/decode.go b/vendor/gopkg.in/yaml.v3/decode.go index be63169b7..df36e3a30 100644 --- a/vendor/gopkg.in/yaml.v3/decode.go +++ b/vendor/gopkg.in/yaml.v3/decode.go @@ -35,6 +35,7 @@ type parser struct { doc *Node anchors map[string]*Node doneInit bool + textless bool } func newParser(b []byte) *parser { @@ -108,14 +109,18 @@ func (p *parser) peek() yaml_event_type_t { func (p *parser) fail() { var where string var line int - if p.parser.problem_mark.line != 0 { + if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.problem_mark.line != 0 { line = p.parser.problem_mark.line // Scanner errors don't iterate line before returning error if p.parser.error == yaml_SCANNER_ERROR { line++ } - } else if p.parser.context_mark.line != 0 { - line = p.parser.context_mark.line } if line != 0 { where = "line " + strconv.Itoa(line) + ": " @@ -169,17 +174,20 @@ func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node { } else if kind == ScalarNode { tag, _ = resolve("", value) } - return &Node{ - Kind: kind, - Tag: tag, - Value: value, - Style: style, - Line: p.event.start_mark.line + 1, - Column: p.event.start_mark.column + 1, - HeadComment: string(p.event.head_comment), - LineComment: string(p.event.line_comment), - FootComment: string(p.event.foot_comment), + n := &Node{ + Kind: kind, + Tag: tag, + Value: value, + Style: style, + } + if !p.textless { + n.Line = p.event.start_mark.line + 1 + n.Column = p.event.start_mark.column + 1 + n.HeadComment = string(p.event.head_comment) + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) } + return n } func (p *parser) parseChild(parent *Node) *Node { @@ -497,8 +505,13 @@ func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) { good = d.mapping(n, out) case SequenceNode: good = d.sequence(n, out) + case 0: + if n.IsZero() { + return d.null(out) + } + fallthrough default: - panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind))) + failf("cannot decode node with unknown kind %d", n.Kind) } return good } @@ -533,6 +546,17 @@ func resetMap(out reflect.Value) { } } +func (d *decoder) null(out reflect.Value) bool { + if out.CanAddr() { + switch out.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + out.Set(reflect.Zero(out.Type())) + return true + } + } + return false +} + func (d *decoder) scalar(n *Node, out reflect.Value) bool { var tag string var resolved interface{} @@ -550,14 +574,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool { } } if resolved == nil { - if out.CanAddr() { - switch out.Kind() { - case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - out.Set(reflect.Zero(out.Type())) - return true - } - } - return false + return d.null(out) } if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { // We've resolved to exactly the type we want, so use that. @@ -791,8 +808,10 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } + mapIsNew := false if out.IsNil() { out.Set(reflect.MakeMap(outt)) + mapIsNew = true } for i := 0; i < l; i += 2 { if isMerge(n.Content[i]) { @@ -809,7 +828,7 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { failf("invalid map key: %#v", k.Interface()) } e := reflect.New(et).Elem() - if d.unmarshal(n.Content[i+1], e) { + if d.unmarshal(n.Content[i+1], e) || n.Content[i+1].ShortTag() == nullTag && (mapIsNew || !out.MapIndex(k).IsValid()) { out.SetMapIndex(k, e) } } diff --git a/vendor/gopkg.in/yaml.v3/emitterc.go b/vendor/gopkg.in/yaml.v3/emitterc.go index ab2a06619..0f47c9ca8 100644 --- a/vendor/gopkg.in/yaml.v3/emitterc.go +++ b/vendor/gopkg.in/yaml.v3/emitterc.go @@ -235,10 +235,13 @@ func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool emitter.indent = 0 } } else if !indentless { - emitter.indent += emitter.best_indent - // [Go] If inside a block sequence item, discount the space taken by the indicator. - if emitter.best_indent > 2 && emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE { - emitter.indent -= 2 + // [Go] This was changed so that indentations are more regular. + if emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE { + // The first indent inside a sequence will just skip the "- " indicator. + emitter.indent += 2 + } else { + // Everything else aligns to the chosen indentation. + emitter.indent = emitter.best_indent*((emitter.indent+emitter.best_indent)/emitter.best_indent) } } return true @@ -725,16 +728,9 @@ func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_e // Expect a block item node. func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { if first { - // [Go] The original logic here would not indent the sequence when inside a mapping. - // In Go we always indent it, but take the sequence indicator out of the indentation. - indentless := emitter.best_indent == 2 && emitter.mapping_context && (emitter.column == 0 || !emitter.indention) - original := emitter.indent - if !yaml_emitter_increase_indent(emitter, false, indentless) { + if !yaml_emitter_increase_indent(emitter, false, false) { return false } - if emitter.indent > original+2 { - emitter.indent -= 2 - } } if event.typ == yaml_SEQUENCE_END_EVENT { emitter.indent = emitter.indents[len(emitter.indents)-1] @@ -785,6 +781,13 @@ func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_ev if !yaml_emitter_write_indent(emitter) { return false } + if len(emitter.line_comment) > 0 { + // [Go] A line comment was provided for the key. That's unusual as the + // scanner associates line comments with the value. Either way, + // save the line comment and render it appropriately later. + emitter.key_line_comment = emitter.line_comment + emitter.line_comment = nil + } if yaml_emitter_check_simple_key(emitter) { emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) return yaml_emitter_emit_node(emitter, event, false, false, true, true) @@ -810,6 +813,27 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_ return false } } + if len(emitter.key_line_comment) > 0 { + // [Go] Line comments are generally associated with the value, but when there's + // no value on the same line as a mapping key they end up attached to the + // key itself. + if event.typ == yaml_SCALAR_EVENT { + if len(emitter.line_comment) == 0 { + // A scalar is coming and it has no line comments by itself yet, + // so just let it handle the line comment as usual. If it has a + // line comment, we can't have both so the one from the key is lost. + emitter.line_comment = emitter.key_line_comment + emitter.key_line_comment = nil + } + } else if event.sequence_style() != yaml_FLOW_SEQUENCE_STYLE && (event.typ == yaml_MAPPING_START_EVENT || event.typ == yaml_SEQUENCE_START_EVENT) { + // An indented block follows, so write the comment right now. + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + if !yaml_emitter_process_line_comment(emitter) { + return false + } + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + } + } emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) if !yaml_emitter_emit_node(emitter, event, false, false, true, false) { return false @@ -823,6 +847,10 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_ return true } +func yaml_emitter_silent_nil_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + return event.typ == yaml_SCALAR_EVENT && event.implicit && !emitter.canonical && len(emitter.scalar_data.value) == 0 +} + // Expect a node. func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, root bool, sequence bool, mapping bool, simple_key bool) bool { @@ -1866,7 +1894,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo if !yaml_emitter_write_block_scalar_hints(emitter, value) { return false } - if !put_break(emitter) { + if !yaml_emitter_process_line_comment(emitter) { return false } //emitter.indention = true @@ -1903,10 +1931,10 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo if !yaml_emitter_write_block_scalar_hints(emitter, value) { return false } - - if !put_break(emitter) { + if !yaml_emitter_process_line_comment(emitter) { return false } + //emitter.indention = true emitter.whitespace = true diff --git a/vendor/gopkg.in/yaml.v3/encode.go b/vendor/gopkg.in/yaml.v3/encode.go index 1f37271ce..de9e72a3e 100644 --- a/vendor/gopkg.in/yaml.v3/encode.go +++ b/vendor/gopkg.in/yaml.v3/encode.go @@ -119,6 +119,14 @@ func (e *encoder) marshal(tag string, in reflect.Value) { case *Node: e.nodev(in) return + case Node: + if !in.CanAddr() { + var n = reflect.New(in.Type()).Elem() + n.Set(in) + in = n + } + e.nodev(in.Addr()) + return case time.Time: e.timev(tag, in) return @@ -422,18 +430,23 @@ func (e *encoder) nodev(in reflect.Value) { } func (e *encoder) node(node *Node, tail string) { + // Zero nodes behave as nil. + if node.Kind == 0 && node.IsZero() { + e.nilv() + return + } + // If the tag was not explicitly requested, and dropping it won't change the // implicit tag of the value, don't include it in the presentation. var tag = node.Tag var stag = shortTag(tag) - var rtag string var forceQuoting bool if tag != "" && node.Style&TaggedStyle == 0 { if node.Kind == ScalarNode { if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 { tag = "" } else { - rtag, _ = resolve("", node.Value) + rtag, _ := resolve("", node.Value) if rtag == stag { tag = "" } else if stag == strTag { @@ -442,6 +455,7 @@ func (e *encoder) node(node *Node, tail string) { } } } else { + var rtag string switch node.Kind { case MappingNode: rtag = mapTag @@ -471,7 +485,7 @@ func (e *encoder) node(node *Node, tail string) { if node.Style&FlowStyle != 0 { style = yaml_FLOW_SEQUENCE_STYLE } - e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)) + e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)) e.event.head_comment = []byte(node.HeadComment) e.emit() for _, node := range node.Content { @@ -487,7 +501,7 @@ func (e *encoder) node(node *Node, tail string) { if node.Style&FlowStyle != 0 { style = yaml_FLOW_MAPPING_STYLE } - yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style) + yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style) e.event.tail_comment = []byte(tail) e.event.head_comment = []byte(node.HeadComment) e.emit() @@ -528,11 +542,11 @@ func (e *encoder) node(node *Node, tail string) { case ScalarNode: value := node.Value if !utf8.ValidString(value) { - if tag == binaryTag { + if stag == binaryTag { failf("explicitly tagged !!binary data must be base64-encoded") } - if tag != "" { - failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + if stag != "" { + failf("cannot marshal invalid UTF-8 data as %s", stag) } // It can't be encoded directly as YAML so use a binary tag // and encode it as base64. @@ -557,5 +571,7 @@ func (e *encoder) node(node *Node, tail string) { } e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail)) + default: + failf("cannot encode node with unknown kind %d", node.Kind) } } diff --git a/vendor/gopkg.in/yaml.v3/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go index aea9050b8..ac66fccc0 100644 --- a/vendor/gopkg.in/yaml.v3/parserc.go +++ b/vendor/gopkg.in/yaml.v3/parserc.go @@ -648,6 +648,10 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i implicit: implicit, style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), } + if parser.stem_comment != nil { + event.head_comment = parser.stem_comment + parser.stem_comment = nil + } return true } if len(anchor) > 0 || len(tag) > 0 { @@ -694,25 +698,13 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e if token.typ == yaml_BLOCK_ENTRY_TOKEN { mark := token.end_mark - prior_head := len(parser.head_comment) + prior_head_len := len(parser.head_comment) skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) token = peek_token(parser) if token == nil { return false } - if prior_head > 0 && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { - // [Go] It's a sequence under a sequence entry, so the former head comment - // is for the list itself, not the first list item under it. - parser.stem_comment = parser.head_comment[:prior_head] - if len(parser.head_comment) == prior_head { - parser.head_comment = nil - } else { - // Copy suffix to prevent very strange bugs if someone ever appends - // further bytes to the prefix in the stem_comment slice above. - parser.head_comment = append([]byte(nil), parser.head_comment[prior_head+1:]...) - } - - } if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) return yaml_parser_parse_node(parser, event, true, false) @@ -754,7 +746,9 @@ func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *y if token.typ == yaml_BLOCK_ENTRY_TOKEN { mark := token.end_mark + prior_head_len := len(parser.head_comment) skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) token = peek_token(parser) if token == nil { return false @@ -780,6 +774,32 @@ func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *y return true } +// Split stem comment from head comment. +// +// When a sequence or map is found under a sequence entry, the former head comment +// is assigned to the underlying sequence or map as a whole, not the individual +// sequence or map entry as would be expected otherwise. To handle this case the +// previous head comment is moved aside as the stem comment. +func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { + if stem_len == 0 { + return + } + + token := peek_token(parser) + if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + return + } + + parser.stem_comment = parser.head_comment[:stem_len] + if len(parser.head_comment) == stem_len { + parser.head_comment = nil + } else { + // Copy suffix to prevent very strange bugs if someone ever appends + // further bytes to the prefix in the stem_comment slice above. + parser.head_comment = append([]byte(nil), parser.head_comment[stem_len+1:]...) + } +} + // Parse the productions: // block_mapping ::= BLOCK-MAPPING_START // ******************* diff --git a/vendor/gopkg.in/yaml.v3/scannerc.go b/vendor/gopkg.in/yaml.v3/scannerc.go index 57e954ca5..ca0070108 100644 --- a/vendor/gopkg.in/yaml.v3/scannerc.go +++ b/vendor/gopkg.in/yaml.v3/scannerc.go @@ -749,6 +749,11 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) { if !ok { return } + if len(parser.tokens) > 0 && parser.tokens[len(parser.tokens)-1].typ == yaml_BLOCK_ENTRY_TOKEN { + // Sequence indicators alone have no line comments. It becomes + // a head comment for whatever follows. + return + } if !yaml_parser_scan_line_comment(parser, comment_mark) { ok = false return @@ -2255,10 +2260,9 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l } } if parser.buffer[parser.buffer_pos] == '#' { - // TODO Test this and then re-enable it. - //if !yaml_parser_scan_line_comment(parser, start_mark) { - // return false - //} + if !yaml_parser_scan_line_comment(parser, start_mark) { + return false + } for !is_breakz(parser.buffer, parser.buffer_pos) { skip(parser) if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { @@ -2856,13 +2860,12 @@ func yaml_parser_scan_line_comment(parser *yaml_parser_t, token_mark yaml_mark_t return false } skip_line(parser) - } else { - if parser.mark.index >= seen { - if len(text) == 0 { - start_mark = parser.mark - } - text = append(text, parser.buffer[parser.buffer_pos]) + } else if parser.mark.index >= seen { + if len(text) == 0 { + start_mark = parser.mark } + text = read(parser, text) + } else { skip(parser) } } @@ -2888,6 +2891,10 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo var token_mark = token.start_mark var start_mark yaml_mark_t + var next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } var recent_empty = false var first_empty = parser.newlines <= 1 @@ -2919,15 +2926,18 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo continue } c := parser.buffer[parser.buffer_pos+peek] - if is_breakz(parser.buffer, parser.buffer_pos+peek) || parser.flow_level > 0 && (c == ']' || c == '}') { + var close_flow = parser.flow_level > 0 && (c == ']' || c == '}') + if close_flow || is_breakz(parser.buffer, parser.buffer_pos+peek) { // Got line break or terminator. - if !recent_empty { - if first_empty && (start_mark.line == foot_line || start_mark.column-1 < parser.indent) { + if close_flow || !recent_empty { + if close_flow || first_empty && (start_mark.line == foot_line && token.typ != yaml_VALUE_TOKEN || start_mark.column-1 < next_indent) { // This is the first empty line and there were no empty lines before, // so this initial part of the comment is a foot of the prior token // instead of being a head for the following one. Split it up. + // Alternatively, this might also be the last comment inside a flow + // scope, so it must be a footer. if len(text) > 0 { - if start_mark.column-1 < parser.indent { + if start_mark.column-1 < next_indent { // If dedented it's unrelated to the prior token. token_mark = start_mark } @@ -2958,7 +2968,7 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo continue } - if len(text) > 0 && column < parser.indent+1 && column != start_mark.column { + if len(text) > 0 && (close_flow || column-1 < next_indent && column != start_mark.column) { // The comment at the different indentation is a foot of the // preceding data rather than a head of the upcoming one. parser.comments = append(parser.comments, yaml_comment_t{ @@ -2999,10 +3009,9 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo return false } skip_line(parser) + } else if parser.mark.index >= seen { + text = read(parser, text) } else { - if parser.mark.index >= seen { - text = append(text, parser.buffer[parser.buffer_pos]) - } skip(parser) } } @@ -3010,6 +3019,10 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo peek = 0 column = 0 line = parser.mark.line + next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } } if len(text) > 0 { diff --git a/vendor/gopkg.in/yaml.v3/yaml.go b/vendor/gopkg.in/yaml.v3/yaml.go index b5d35a50d..8cec6da48 100644 --- a/vendor/gopkg.in/yaml.v3/yaml.go +++ b/vendor/gopkg.in/yaml.v3/yaml.go @@ -89,7 +89,7 @@ func Unmarshal(in []byte, out interface{}) (err error) { return unmarshal(in, out, false) } -// A Decorder reads and decodes YAML values from an input stream. +// A Decoder reads and decodes YAML values from an input stream. type Decoder struct { parser *parser knownFields bool @@ -194,7 +194,7 @@ func unmarshal(in []byte, out interface{}, strict bool) (err error) { // Zero valued structs will be omitted if all their public // fields are zero, unless they implement an IsZero // method (see the IsZeroer interface type), in which -// case the field will be included if that method returns true. +// case the field will be excluded if IsZero returns true. // // flow Marshal using a flow style (useful for structs, // sequences and maps). @@ -252,6 +252,24 @@ func (e *Encoder) Encode(v interface{}) (err error) { return nil } +// Encode encodes value v and stores its representation in n. +// +// See the documentation for Marshal for details about the +// conversion of Go values into YAML. +func (n *Node) Encode(v interface{}) (err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(v)) + e.finish() + p := newParser(e.out) + p.textless = true + defer p.destroy() + doc := p.parse() + *n = *doc.Content[0] + return nil +} + // SetIndent changes the used indentation used when encoding. func (e *Encoder) SetIndent(spaces int) { if spaces < 0 { @@ -328,6 +346,12 @@ const ( // and maps, Node is an intermediate representation that allows detailed // control over the content being decoded or encoded. // +// It's worth noting that although Node offers access into details such as +// line numbers, colums, and comments, the content when re-encoded will not +// have its original textual representation preserved. An effort is made to +// render the data plesantly, and to preserve comments near the data they +// describe, though. +// // Values that make use of the Node type interact with the yaml package in the // same way any other type would do, by encoding and decoding yaml data // directly or indirectly into them. @@ -391,6 +415,13 @@ type Node struct { Column int } +// IsZero returns whether the node has all of its fields unset. +func (n *Node) IsZero() bool { + return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil && + n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0 +} + + // LongTag returns the long form of the tag that indicates the data type for // the node. If the Tag field isn't explicitly defined, one will be computed // based on the node properties. @@ -418,6 +449,11 @@ func (n *Node) ShortTag() string { case ScalarNode: tag, _ := resolve("", n.Value) return tag + case 0: + // Special case to make the zero value convenient. + if n.IsZero() { + return nullTag + } } return "" } diff --git a/vendor/gopkg.in/yaml.v3/yamlh.go b/vendor/gopkg.in/yaml.v3/yamlh.go index 2719cfbb0..7c6d00770 100644 --- a/vendor/gopkg.in/yaml.v3/yamlh.go +++ b/vendor/gopkg.in/yaml.v3/yamlh.go @@ -787,6 +787,8 @@ type yaml_emitter_t struct { foot_comment []byte tail_comment []byte + key_line_comment []byte + // Dumper stuff opened bool // If the stream was already opened? diff --git a/vendor/modules.txt b/vendor/modules.txt index 62cce2da4..e4452b075 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -61,7 +61,7 @@ github.com/eknkc/amber/parser github.com/fatih/color # github.com/fatih/structs v1.1.0 github.com/fatih/structs -# github.com/go-co-op/gocron v0.3.3 +# github.com/go-co-op/gocron v1.0.0 ## explicit github.com/go-co-op/gocron # github.com/go-ole/go-ole v1.2.4 @@ -335,6 +335,9 @@ golang.org/x/oauth2/google golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt +# golang.org/x/sync v0.0.0-20201207232520-09787c993a3a +golang.org/x/sync/semaphore +golang.org/x/sync/singleflight # golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f golang.org/x/sys/cpu golang.org/x/sys/internal/unsafeheader @@ -393,5 +396,5 @@ google.golang.org/protobuf/runtime/protoiface google.golang.org/protobuf/runtime/protoimpl # gopkg.in/ini.v1 v1.51.1 gopkg.in/ini.v1 -# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3