init - add project files
This commit is contained in:
20
vendor/github.com/go-co-op/gocron/v2/.gitignore
generated
vendored
Normal file
20
vendor/github.com/go-co-op/gocron/v2/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
local_testing
|
||||
coverage.out
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
|
||||
# IDE project files
|
||||
.idea
|
||||
44
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
Normal file
44
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
|
||||
issues:
|
||||
max-same-issues: 100
|
||||
include:
|
||||
- EXC0012
|
||||
- EXC0014
|
||||
exclude-dirs:
|
||||
- local
|
||||
exclude-rules:
|
||||
- path: example_test.go
|
||||
linters:
|
||||
- revive
|
||||
text: "seems to be unused"
|
||||
fix: true
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- exportloopref
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
output:
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
uniq-by-line: true
|
||||
path-prefix: ""
|
||||
sort-results: true
|
||||
24
vendor/github.com/go-co-op/gocron/v2/.pre-commit-config.yaml
generated
vendored
Normal file
24
vendor/github.com/go-co-op/gocron/v2/.pre-commit-config.yaml
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-merge-conflict
|
||||
- id: check-yaml
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.61.0
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/TekWizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-fumpt
|
||||
args:
|
||||
- -w
|
||||
- id: go-mod-tidy
|
||||
73
vendor/github.com/go-co-op/gocron/v2/CODE_OF_CONDUCT.md
generated
vendored
Normal file
73
vendor/github.com/go-co-op/gocron/v2/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -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
|
||||
38
vendor/github.com/go-co-op/gocron/v2/CONTRIBUTING.md
generated
vendored
Normal file
38
vendor/github.com/go-co-op/gocron/v2/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# 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`
|
||||
|
||||
## 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.
|
||||
21
vendor/github.com/go-co-op/gocron/v2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/go-co-op/gocron/v2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014, 辣椒面
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
22
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
Normal file
22
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.PHONY: fmt lint test mocks test_coverage test_ci
|
||||
|
||||
GO_PKGS := $(shell go list -f {{.Dir}} ./...)
|
||||
|
||||
fmt:
|
||||
@go list -f {{.Dir}} ./... | xargs -I{} gofmt -w -s {}
|
||||
|
||||
lint:
|
||||
@grep "^func [a-zA-Z]" example_test.go | sort -c
|
||||
@golangci-lint run
|
||||
|
||||
test:
|
||||
@go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
|
||||
|
||||
test_coverage:
|
||||
@go test -race -v $(GO_FLAGS) -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PKGS)
|
||||
|
||||
test_ci:
|
||||
@TEST_ENV=ci go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
|
||||
|
||||
mocks:
|
||||
@go generate ./...
|
||||
180
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
Normal file
180
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
# gocron: A Golang Job Scheduling Package
|
||||
|
||||
[](https://github.com/go-co-op/gocron/actions)
|
||||
 [](https://pkg.go.dev/github.com/go-co-op/gocron/v2)
|
||||
|
||||
gocron is a job scheduling package which lets you run Go functions at pre-determined intervals.
|
||||
|
||||
If you want to chat, you can find us on Slack at
|
||||
[<img src="https://img.shields.io/badge/gophers-gocron-brightgreen?logo=slack">](https://gophers.slack.com/archives/CQ7T0T1FW)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
go get github.com/go-co-op/gocron/v2
|
||||
```
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// create a scheduler
|
||||
s, err := gocron.NewScheduler()
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// add a job to the scheduler
|
||||
j, err := s.NewJob(
|
||||
gocron.DurationJob(
|
||||
10*time.Second,
|
||||
),
|
||||
gocron.NewTask(
|
||||
func(a string, b int) {
|
||||
// do things
|
||||
},
|
||||
"hello",
|
||||
1,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
// each job has a unique id
|
||||
fmt.Println(j.ID())
|
||||
|
||||
// start the scheduler
|
||||
s.Start()
|
||||
|
||||
// block until you are ready to shut down
|
||||
select {
|
||||
case <-time.After(time.Minute):
|
||||
}
|
||||
|
||||
// when you're done, shut it down
|
||||
err = s.Shutdown()
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [Go doc examples](https://pkg.go.dev/github.com/go-co-op/gocron/v2#pkg-examples)
|
||||
- [Examples directory](examples)
|
||||
|
||||
## Concepts
|
||||
|
||||
- **Job**: The job encapsulates a "task", which is made up of a go function and any function parameters. The Job then
|
||||
provides the scheduler with the time the job should next be scheduled to run.
|
||||
- **Scheduler**: The scheduler keeps track of all the jobs and sends each job to the executor when
|
||||
it is ready to be run.
|
||||
- **Executor**: The executor calls the job's task and manages the complexities of different job
|
||||
execution timing requirements (e.g. singletons that shouldn't overrun each other, limiting the max number of jobs running)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
### Job types
|
||||
Jobs can be run at various intervals.
|
||||
- [**Duration**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#DurationJob):
|
||||
Jobs can be run at a fixed `time.Duration`.
|
||||
- [**Random duration**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#DurationRandomJob):
|
||||
Jobs can be run at a random `time.Duration` between a min and max.
|
||||
- [**Cron**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#CronJob):
|
||||
Jobs can be run using a crontab.
|
||||
- [**Daily**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#DailyJob):
|
||||
Jobs can be run every x days at specific times.
|
||||
- [**Weekly**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WeeklyJob):
|
||||
Jobs can be run every x weeks on specific days of the week and at specific times.
|
||||
- [**Monthly**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#MonthlyJob):
|
||||
Jobs can be run every x months on specific days of the month and at specific times.
|
||||
- [**One time**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#OneTimeJob):
|
||||
Jobs can be run at specific time(s) (either once or many times).
|
||||
|
||||
### Concurrency Limits
|
||||
Jobs can be limited individually or across the entire scheduler.
|
||||
- [**Per job limiting with singleton mode**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithSingletonMode):
|
||||
Jobs can be limited to a single concurrent execution that either reschedules (skips overlapping executions)
|
||||
or queues (waits for the previous execution to finish).
|
||||
- [**Per scheduler limiting with limit mode**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithLimitConcurrentJobs):
|
||||
Jobs can be limited to a certain number of concurrent executions across the entire scheduler
|
||||
using either reschedule (skip when the limit is met) or queue (jobs are added to a queue to
|
||||
wait for the limit to be available).
|
||||
- **Note:** A scheduler limit and a job limit can both be enabled.
|
||||
|
||||
### Distributed instances of gocron
|
||||
Multiple instances of gocron can be run.
|
||||
- [**Elector**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithDistributedElector):
|
||||
An elector can be used to elect a single instance of gocron to run as the primary with the
|
||||
other instances checking to see if a new leader needs to be elected.
|
||||
- Implementations: [go-co-op electors](https://github.com/go-co-op?q=-elector&type=all&language=&sort=)
|
||||
(don't see what you need? request on slack to get a repo created to contribute it!)
|
||||
- [**Locker**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithDistributedLocker):
|
||||
A locker can be used to lock each run of a job to a single instance of gocron.
|
||||
Locker can be at job or scheduler, if it is defined both at job and scheduler then locker of job will take precedence.
|
||||
- Implementations: [go-co-op lockers](https://github.com/go-co-op?q=-lock&type=all&language=&sort=)
|
||||
(don't see what you need? request on slack to get a repo created to contribute it!)
|
||||
|
||||
### Events
|
||||
Job events can trigger actions.
|
||||
- [**Listeners**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithEventListeners):
|
||||
Can be added to a job, with [event listeners](https://pkg.go.dev/github.com/go-co-op/gocron/v2#EventListener),
|
||||
or all jobs across the
|
||||
[scheduler](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithGlobalJobOptions)
|
||||
to listen for job events and trigger actions.
|
||||
|
||||
### Options
|
||||
Many job and scheduler options are available.
|
||||
- [**Job options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#JobOption):
|
||||
Job options can be set when creating a job using `NewJob`.
|
||||
- [**Global job options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithGlobalJobOptions):
|
||||
Global job options can be set when creating a scheduler using `NewScheduler`
|
||||
and the `WithGlobalJobOptions` option.
|
||||
- [**Scheduler options**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#SchedulerOption):
|
||||
Scheduler options can be set when creating a scheduler using `NewScheduler`.
|
||||
|
||||
### Logging
|
||||
Logs can be enabled.
|
||||
- [Logger](https://pkg.go.dev/github.com/go-co-op/gocron/v2#Logger):
|
||||
The Logger interface can be implemented with your desired logging library.
|
||||
The provided NewLogger uses the standard library's log package.
|
||||
|
||||
### Metrics
|
||||
Metrics may be collected from the execution of each job.
|
||||
- [**Monitor**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#Monitor):
|
||||
- [**MonitorStatus**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#MonitorStatus) (includes status and error (if any) of the Job)
|
||||
A monitor can be used to collect metrics for each job from a scheduler.
|
||||
- Implementations: [go-co-op monitors](https://github.com/go-co-op?q=-monitor&type=all&language=&sort=)
|
||||
(don't see what you need? request on slack to get a repo created to contribute it!)
|
||||
|
||||
### Testing
|
||||
The gocron library is set up to enable testing.
|
||||
- Mocks are provided in [the mock package](mocks) using [gomock](https://github.com/uber-go/mock).
|
||||
- Time can be mocked by passing in a [FakeClock](https://pkg.go.dev/github.com/jonboulle/clockwork#FakeClock)
|
||||
to [WithClock](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithClock) -
|
||||
see the [example on WithClock](https://pkg.go.dev/github.com/go-co-op/gocron/v2#example-WithClock).
|
||||
|
||||
## Supporters
|
||||
|
||||
We appreciate the support for free and open source software!
|
||||
|
||||
This project is supported by:
|
||||
|
||||
[Jetbrains](https://www.jetbrains.com/?from=gocron)
|
||||

|
||||
|
||||
|
||||
[Sentry](https://sentry.io/welcome/)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#go-co-op/gocron&Date)
|
||||
16
vendor/github.com/go-co-op/gocron/v2/SECURITY.md
generated
vendored
Normal file
16
vendor/github.com/go-co-op/gocron/v2/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The current plan is to maintain version 2 as long as possible incorporating any necessary security patches. Version 1 is deprecated and will no longer be patched.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.x.x | :heavy_multiplication_x: |
|
||||
| 2.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Vulnerabilities can be reported by [opening an issue](https://github.com/go-co-op/gocron/issues/new/choose) or reaching out on Slack: [<img src="https://img.shields.io/badge/gophers-gocron-brightgreen?logo=slack">](https://gophers.slack.com/archives/CQ7T0T1FW)
|
||||
|
||||
We will do our best to address any vulnerabilities in an expeditious manner.
|
||||
30
vendor/github.com/go-co-op/gocron/v2/distributed.go
generated
vendored
Normal file
30
vendor/github.com/go-co-op/gocron/v2/distributed.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:generate mockgen -destination=mocks/distributed.go -package=gocronmocks . Elector,Locker,Lock
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Elector determines the leader from instances asking to be the leader. Only
|
||||
// the leader runs jobs. If the leader goes down, a new leader will be elected.
|
||||
type Elector interface {
|
||||
// IsLeader should return nil if the job should be scheduled by the instance
|
||||
// making the request and an error if the job should not be scheduled.
|
||||
IsLeader(context.Context) error
|
||||
}
|
||||
|
||||
// Locker represents the required interface to lock jobs when running multiple schedulers.
|
||||
// The lock is held for the duration of the job's run, and it is expected that the
|
||||
// locker implementation handles time splay between schedulers.
|
||||
// The lock key passed is the job's name - which, if not set, defaults to the
|
||||
// go function's name, e.g. "pkg.myJob" for func myJob() {} in pkg
|
||||
type Locker interface {
|
||||
// Lock if an error is returned by lock, the job will not be scheduled.
|
||||
Lock(ctx context.Context, key string) (Lock, error)
|
||||
}
|
||||
|
||||
// Lock represents an obtained lock. The lock is released after the execution of the job
|
||||
// by the scheduler.
|
||||
type Lock interface {
|
||||
Unlock(ctx context.Context) error
|
||||
}
|
||||
65
vendor/github.com/go-co-op/gocron/v2/errors.go
generated
vendored
Normal file
65
vendor/github.com/go-co-op/gocron/v2/errors.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package gocron
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Public error definitions
|
||||
var (
|
||||
ErrCronJobInvalid = fmt.Errorf("gocron: CronJob: invalid crontab")
|
||||
ErrCronJobParse = fmt.Errorf("gocron: CronJob: crontab parse failure")
|
||||
ErrDailyJobAtTimeNil = fmt.Errorf("gocron: DailyJob: atTime within atTimes must not be nil")
|
||||
ErrDailyJobAtTimesNil = fmt.Errorf("gocron: DailyJob: atTimes must not be nil")
|
||||
ErrDailyJobHours = fmt.Errorf("gocron: DailyJob: atTimes hours must be between 0 and 23 inclusive")
|
||||
ErrDailyJobZeroInterval = fmt.Errorf("gocron: DailyJob: interval must be greater than 0")
|
||||
ErrDailyJobMinutesSeconds = fmt.Errorf("gocron: DailyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
|
||||
ErrDurationJobIntervalZero = fmt.Errorf("gocron: DurationJob: time interval is 0")
|
||||
ErrDurationRandomJobMinMax = fmt.Errorf("gocron: DurationRandomJob: minimum duration must be less than maximum duration")
|
||||
ErrEventListenerFuncNil = fmt.Errorf("gocron: eventListenerFunc must not be nil")
|
||||
ErrJobNotFound = fmt.Errorf("gocron: job not found")
|
||||
ErrJobRunNowFailed = fmt.Errorf("gocron: Job: RunNow: scheduler unreachable")
|
||||
ErrMonthlyJobDays = fmt.Errorf("gocron: MonthlyJob: daysOfTheMonth must be between 31 and -31 inclusive, and not 0")
|
||||
ErrMonthlyJobAtTimeNil = fmt.Errorf("gocron: MonthlyJob: atTime within atTimes must not be nil")
|
||||
ErrMonthlyJobAtTimesNil = fmt.Errorf("gocron: MonthlyJob: atTimes must not be nil")
|
||||
ErrMonthlyJobDaysNil = fmt.Errorf("gocron: MonthlyJob: daysOfTheMonth must not be nil")
|
||||
ErrMonthlyJobHours = fmt.Errorf("gocron: MonthlyJob: atTimes hours must be between 0 and 23 inclusive")
|
||||
ErrMonthlyJobZeroInterval = fmt.Errorf("gocron: MonthlyJob: interval must be greater than 0")
|
||||
ErrMonthlyJobMinutesSeconds = fmt.Errorf("gocron: MonthlyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
|
||||
ErrNewJobTaskNil = fmt.Errorf("gocron: NewJob: Task must not be nil")
|
||||
ErrNewJobTaskNotFunc = fmt.Errorf("gocron: NewJob: Task.Function must be of kind reflect.Func")
|
||||
ErrNewJobWrongNumberOfParameters = fmt.Errorf("gocron: NewJob: Number of provided parameters does not match expected")
|
||||
ErrNewJobWrongTypeOfParameters = fmt.Errorf("gocron: NewJob: Type of provided parameters does not match expected")
|
||||
ErrOneTimeJobStartDateTimePast = fmt.Errorf("gocron: OneTimeJob: start must not be in the past")
|
||||
ErrStopExecutorTimedOut = fmt.Errorf("gocron: timed out waiting for executor to stop")
|
||||
ErrStopJobsTimedOut = fmt.Errorf("gocron: timed out waiting for jobs to finish")
|
||||
ErrStopSchedulerTimedOut = fmt.Errorf("gocron: timed out waiting for scheduler to stop")
|
||||
ErrWeeklyJobAtTimeNil = fmt.Errorf("gocron: WeeklyJob: atTime within atTimes must not be nil")
|
||||
ErrWeeklyJobAtTimesNil = fmt.Errorf("gocron: WeeklyJob: atTimes must not be nil")
|
||||
ErrWeeklyJobDaysOfTheWeekNil = fmt.Errorf("gocron: WeeklyJob: daysOfTheWeek must not be nil")
|
||||
ErrWeeklyJobHours = fmt.Errorf("gocron: WeeklyJob: atTimes hours must be between 0 and 23 inclusive")
|
||||
ErrWeeklyJobZeroInterval = fmt.Errorf("gocron: WeeklyJob: interval must be greater than 0")
|
||||
ErrWeeklyJobMinutesSeconds = fmt.Errorf("gocron: WeeklyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
|
||||
ErrPanicRecovered = fmt.Errorf("gocron: panic recovered")
|
||||
ErrWithClockNil = fmt.Errorf("gocron: WithClock: clock must not be nil")
|
||||
ErrWithContextNil = fmt.Errorf("gocron: WithContext: context must not be nil")
|
||||
ErrWithDistributedElectorNil = fmt.Errorf("gocron: WithDistributedElector: elector must not be nil")
|
||||
ErrWithDistributedLockerNil = fmt.Errorf("gocron: WithDistributedLocker: locker must not be nil")
|
||||
ErrWithDistributedJobLockerNil = fmt.Errorf("gocron: WithDistributedJobLocker: locker must not be nil")
|
||||
ErrWithIdentifierNil = fmt.Errorf("gocron: WithIdentifier: identifier must not be nil")
|
||||
ErrWithLimitConcurrentJobsZero = fmt.Errorf("gocron: WithLimitConcurrentJobs: limit must be greater than 0")
|
||||
ErrWithLocationNil = fmt.Errorf("gocron: WithLocation: location must not be nil")
|
||||
ErrWithLoggerNil = fmt.Errorf("gocron: WithLogger: logger must not be nil")
|
||||
ErrWithMonitorNil = fmt.Errorf("gocron: WithMonitor: monitor must not be nil")
|
||||
ErrWithNameEmpty = fmt.Errorf("gocron: WithName: name must not be empty")
|
||||
ErrWithStartDateTimePast = fmt.Errorf("gocron: WithStartDateTime: start must not be in the past")
|
||||
ErrWithStopDateTimePast = fmt.Errorf("gocron: WithStopDateTime: end must not be in the past")
|
||||
ErrStartTimeLaterThanEndTime = fmt.Errorf("gocron: WithStartDateTime: start must not be later than end")
|
||||
ErrStopTimeEarlierThanStartTime = fmt.Errorf("gocron: WithStopDateTime: end must not be earlier than start")
|
||||
ErrWithStopTimeoutZeroOrNegative = fmt.Errorf("gocron: WithStopTimeout: timeout must be greater than 0")
|
||||
)
|
||||
|
||||
// internal errors
|
||||
var (
|
||||
errAtTimeNil = fmt.Errorf("errAtTimeNil")
|
||||
errAtTimesNil = fmt.Errorf("errAtTimesNil")
|
||||
errAtTimeHours = fmt.Errorf("errAtTimeHours")
|
||||
errAtTimeMinSec = fmt.Errorf("errAtTimeMinSec")
|
||||
)
|
||||
545
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
Normal file
545
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type executor struct {
|
||||
// context used for shutting down
|
||||
ctx context.Context
|
||||
// cancel used by the executor to signal a stop of it's functions
|
||||
cancel context.CancelFunc
|
||||
// clock used for regular time or mocking time
|
||||
clock clockwork.Clock
|
||||
// the executor's logger
|
||||
logger Logger
|
||||
|
||||
// receives jobs scheduled to execute
|
||||
jobsIn chan jobIn
|
||||
// sends out jobs for rescheduling
|
||||
jobsOutForRescheduling chan uuid.UUID
|
||||
// sends out jobs once completed
|
||||
jobsOutCompleted chan uuid.UUID
|
||||
// used to request jobs from the scheduler
|
||||
jobOutRequest chan jobOutRequest
|
||||
|
||||
// used by the executor to receive a stop signal from the scheduler
|
||||
stopCh chan struct{}
|
||||
// the timeout value when stopping
|
||||
stopTimeout time.Duration
|
||||
// used to signal that the executor has completed shutdown
|
||||
done chan error
|
||||
|
||||
// runners for any singleton type jobs
|
||||
// map[uuid.UUID]singletonRunner
|
||||
singletonRunners *sync.Map
|
||||
// config for limit mode
|
||||
limitMode *limitModeConfig
|
||||
// the elector when running distributed instances
|
||||
elector Elector
|
||||
// the locker when running distributed instances
|
||||
locker Locker
|
||||
// monitor for reporting metrics
|
||||
monitor Monitor
|
||||
// monitorStatus for reporting metrics
|
||||
monitorStatus MonitorStatus
|
||||
}
|
||||
|
||||
type jobIn struct {
|
||||
id uuid.UUID
|
||||
shouldSendOut bool
|
||||
}
|
||||
|
||||
type singletonRunner struct {
|
||||
in chan jobIn
|
||||
rescheduleLimiter chan struct{}
|
||||
}
|
||||
|
||||
type limitModeConfig struct {
|
||||
started bool
|
||||
mode LimitMode
|
||||
limit uint
|
||||
rescheduleLimiter chan struct{}
|
||||
in chan jobIn
|
||||
// singletonJobs is used to track singleton jobs that are running
|
||||
// in the limit mode runner. This is used to prevent the same job
|
||||
// from running multiple times across limit mode runners when both
|
||||
// a limit mode and singleton mode are enabled.
|
||||
singletonJobs map[uuid.UUID]struct{}
|
||||
singletonJobsMu sync.Mutex
|
||||
}
|
||||
|
||||
func (e *executor) start() {
|
||||
e.logger.Debug("gocron: executor started")
|
||||
|
||||
// creating the executor's context here as the executor
|
||||
// is the only goroutine that should access this context
|
||||
// any other uses within the executor should create a context
|
||||
// using the executor context as parent.
|
||||
e.ctx, e.cancel = context.WithCancel(context.Background())
|
||||
|
||||
// the standardJobsWg tracks
|
||||
standardJobsWg := &waitGroupWithMutex{}
|
||||
|
||||
singletonJobsWg := &waitGroupWithMutex{}
|
||||
|
||||
limitModeJobsWg := &waitGroupWithMutex{}
|
||||
|
||||
// create a fresh map for tracking singleton runners
|
||||
e.singletonRunners = &sync.Map{}
|
||||
|
||||
// start the for leap that is the executor
|
||||
// selecting on channels for work to do
|
||||
for {
|
||||
select {
|
||||
// job ids in are sent from 1 of 2 places:
|
||||
// 1. the scheduler sends directly when jobs
|
||||
// are run immediately.
|
||||
// 2. sent from time.AfterFuncs in which job schedules
|
||||
// are spun up by the scheduler
|
||||
case jIn := <-e.jobsIn:
|
||||
select {
|
||||
case <-e.stopCh:
|
||||
e.stop(standardJobsWg, singletonJobsWg, limitModeJobsWg)
|
||||
return
|
||||
default:
|
||||
}
|
||||
// this context is used to handle cancellation of the executor
|
||||
// on requests for a job to the scheduler via requestJobCtx
|
||||
ctx, cancel := context.WithCancel(e.ctx)
|
||||
|
||||
if e.limitMode != nil && !e.limitMode.started {
|
||||
// check if we are already running the limit mode runners
|
||||
// if not, spin up the required number i.e. limit!
|
||||
e.limitMode.started = true
|
||||
for i := e.limitMode.limit; i > 0; i-- {
|
||||
limitModeJobsWg.Add(1)
|
||||
go e.limitModeRunner("limitMode-"+strconv.Itoa(int(i)), e.limitMode.in, limitModeJobsWg, e.limitMode.mode, e.limitMode.rescheduleLimiter)
|
||||
}
|
||||
}
|
||||
|
||||
// spin off into a goroutine to unblock the executor and
|
||||
// allow for processing for more work
|
||||
go func() {
|
||||
// make sure to cancel the above context per the docs
|
||||
// // Canceling this context releases resources associated with it, so code should
|
||||
// // call cancel as soon as the operations running in this Context complete.
|
||||
defer cancel()
|
||||
|
||||
// check for limit mode - this spins up a separate runner which handles
|
||||
// limiting the total number of concurrently running jobs
|
||||
if e.limitMode != nil {
|
||||
if e.limitMode.mode == LimitModeReschedule {
|
||||
select {
|
||||
// rescheduleLimiter is a channel the size of the limit
|
||||
// this blocks publishing to the channel and keeps
|
||||
// the executor from building up a waiting queue
|
||||
// and forces rescheduling
|
||||
case e.limitMode.rescheduleLimiter <- struct{}{}:
|
||||
e.limitMode.in <- jIn
|
||||
default:
|
||||
// all runners are busy, reschedule the work for later
|
||||
// which means we just skip it here and do nothing
|
||||
// TODO when metrics are added, this should increment a rescheduled metric
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
}
|
||||
} else {
|
||||
// since we're not using LimitModeReschedule, but instead using LimitModeWait
|
||||
// we do want to queue up the work to the limit mode runners and allow them
|
||||
// to work through the channel backlog. A hard limit of 1000 is in place
|
||||
// at which point this call would block.
|
||||
// TODO when metrics are added, this should increment a wait metric
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.limitMode.in <- jIn
|
||||
}
|
||||
} else {
|
||||
// no limit mode, so we're either running a regular job or
|
||||
// a job with a singleton mode
|
||||
//
|
||||
// get the job, so we can figure out what kind it is and how
|
||||
// to execute it
|
||||
j := requestJobCtx(ctx, jIn.id, e.jobOutRequest)
|
||||
if j == nil {
|
||||
// safety check as it'd be strange bug if this occurred
|
||||
return
|
||||
}
|
||||
if j.singletonMode {
|
||||
// for singleton mode, get the existing runner for the job
|
||||
// or spin up a new one
|
||||
runner := &singletonRunner{}
|
||||
runnerSrc, ok := e.singletonRunners.Load(jIn.id)
|
||||
if !ok {
|
||||
runner.in = make(chan jobIn, 1000)
|
||||
if j.singletonLimitMode == LimitModeReschedule {
|
||||
runner.rescheduleLimiter = make(chan struct{}, 1)
|
||||
}
|
||||
e.singletonRunners.Store(jIn.id, runner)
|
||||
singletonJobsWg.Add(1)
|
||||
go e.singletonModeRunner("singleton-"+jIn.id.String(), runner.in, singletonJobsWg, j.singletonLimitMode, runner.rescheduleLimiter)
|
||||
} else {
|
||||
runner = runnerSrc.(*singletonRunner)
|
||||
}
|
||||
|
||||
if j.singletonLimitMode == LimitModeReschedule {
|
||||
// reschedule mode uses the limiter channel to check
|
||||
// for a running job and reschedules if the channel is full.
|
||||
select {
|
||||
case runner.rescheduleLimiter <- struct{}{}:
|
||||
runner.in <- jIn
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
default:
|
||||
// runner is busy, reschedule the work for later
|
||||
// which means we just skip it here and do nothing
|
||||
e.incrementJobCounter(*j, SingletonRescheduled)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
}
|
||||
} else {
|
||||
// wait mode, fill up that queue (buffered channel, so it's ok)
|
||||
runner.in <- jIn
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-e.stopCh:
|
||||
e.stop(standardJobsWg, singletonJobsWg, limitModeJobsWg)
|
||||
return
|
||||
default:
|
||||
}
|
||||
// we've gotten to the basic / standard jobs --
|
||||
// the ones without anything special that just want
|
||||
// to be run. Add to the WaitGroup so that
|
||||
// stopping or shutting down can wait for the jobs to
|
||||
// complete.
|
||||
standardJobsWg.Add(1)
|
||||
go func(j internalJob) {
|
||||
e.runJob(j, jIn)
|
||||
standardJobsWg.Done()
|
||||
}(*j)
|
||||
}
|
||||
}
|
||||
}()
|
||||
case <-e.stopCh:
|
||||
e.stop(standardJobsWg, singletonJobsWg, limitModeJobsWg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) sendOutForRescheduling(jIn *jobIn) {
|
||||
if jIn.shouldSendOut {
|
||||
select {
|
||||
case e.jobsOutForRescheduling <- jIn.id:
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
// we need to set this to false now, because to handle
|
||||
// non-limit jobs, we send out from the e.runJob function
|
||||
// and in this case we don't want to send out twice.
|
||||
jIn.shouldSendOut = false
|
||||
}
|
||||
|
||||
func (e *executor) limitModeRunner(name string, in chan jobIn, wg *waitGroupWithMutex, limitMode LimitMode, rescheduleLimiter chan struct{}) {
|
||||
e.logger.Debug("gocron: limitModeRunner starting", "name", name)
|
||||
for {
|
||||
select {
|
||||
case jIn := <-in:
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
e.logger.Debug("gocron: limitModeRunner shutting down", "name", name)
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(e.ctx)
|
||||
j := requestJobCtx(ctx, jIn.id, e.jobOutRequest)
|
||||
cancel()
|
||||
if j != nil {
|
||||
if j.singletonMode {
|
||||
e.limitMode.singletonJobsMu.Lock()
|
||||
_, ok := e.limitMode.singletonJobs[jIn.id]
|
||||
if ok {
|
||||
// this job is already running, so don't run it
|
||||
// but instead reschedule it
|
||||
e.limitMode.singletonJobsMu.Unlock()
|
||||
if jIn.shouldSendOut {
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case <-j.ctx.Done():
|
||||
return
|
||||
case e.jobsOutForRescheduling <- j.id:
|
||||
}
|
||||
}
|
||||
// remove the limiter block, as this particular job
|
||||
// was a singleton already running, and we want to
|
||||
// allow another job to be scheduled
|
||||
if limitMode == LimitModeReschedule {
|
||||
<-rescheduleLimiter
|
||||
}
|
||||
continue
|
||||
}
|
||||
e.limitMode.singletonJobs[jIn.id] = struct{}{}
|
||||
e.limitMode.singletonJobsMu.Unlock()
|
||||
}
|
||||
e.runJob(*j, jIn)
|
||||
|
||||
if j.singletonMode {
|
||||
e.limitMode.singletonJobsMu.Lock()
|
||||
delete(e.limitMode.singletonJobs, jIn.id)
|
||||
e.limitMode.singletonJobsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// remove the limiter block to allow another job to be scheduled
|
||||
if limitMode == LimitModeReschedule {
|
||||
<-rescheduleLimiter
|
||||
}
|
||||
case <-e.ctx.Done():
|
||||
e.logger.Debug("limitModeRunner shutting down", "name", name)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) singletonModeRunner(name string, in chan jobIn, wg *waitGroupWithMutex, limitMode LimitMode, rescheduleLimiter chan struct{}) {
|
||||
e.logger.Debug("gocron: singletonModeRunner starting", "name", name)
|
||||
for {
|
||||
select {
|
||||
case jIn := <-in:
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
e.logger.Debug("gocron: singletonModeRunner shutting down", "name", name)
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(e.ctx)
|
||||
j := requestJobCtx(ctx, jIn.id, e.jobOutRequest)
|
||||
cancel()
|
||||
if j != nil {
|
||||
// need to set shouldSendOut = false here, as there is a duplicative call to sendOutForRescheduling
|
||||
// inside the runJob function that needs to be skipped. sendOutForRescheduling is previously called
|
||||
// when the job is sent to the singleton mode runner.
|
||||
jIn.shouldSendOut = false
|
||||
e.runJob(*j, jIn)
|
||||
}
|
||||
|
||||
// remove the limiter block to allow another job to be scheduled
|
||||
if limitMode == LimitModeReschedule {
|
||||
<-rescheduleLimiter
|
||||
}
|
||||
case <-e.ctx.Done():
|
||||
e.logger.Debug("singletonModeRunner shutting down", "name", name)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) runJob(j internalJob, jIn jobIn) {
|
||||
if j.ctx == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case <-j.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if j.stopTimeReached(e.clock.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
if e.elector != nil {
|
||||
if err := e.elector.IsLeader(j.ctx); err != nil {
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
} else if !j.disabledLocker && j.locker != nil {
|
||||
lock, err := j.locker.Lock(j.ctx, j.name)
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
} else if !j.disabledLocker && e.locker != nil {
|
||||
lock, err := e.locker.Lock(j.ctx, j.name)
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
}
|
||||
|
||||
_ = callJobFuncWithParams(j.beforeJobRuns, j.id, j.name)
|
||||
|
||||
err := callJobFuncWithParams(j.beforeJobRunsSkipIfBeforeFuncErrors, j.id, j.name)
|
||||
if err != nil {
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
|
||||
select {
|
||||
case e.jobsOutCompleted <- j.id:
|
||||
case <-e.ctx.Done():
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
select {
|
||||
case e.jobsOutCompleted <- j.id:
|
||||
case <-e.ctx.Done():
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
if j.afterJobRunsWithPanic != nil {
|
||||
err = e.callJobWithRecover(j)
|
||||
} else {
|
||||
err = callJobFuncWithParams(j.function, j.parameters...)
|
||||
}
|
||||
e.recordJobTiming(startTime, time.Now(), j)
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterJobRunsWithError, j.id, j.name, err)
|
||||
e.incrementJobCounter(j, Fail)
|
||||
e.recordJobTimingWithStatus(startTime, time.Now(), j, Fail, err)
|
||||
} else {
|
||||
_ = callJobFuncWithParams(j.afterJobRuns, j.id, j.name)
|
||||
e.incrementJobCounter(j, Success)
|
||||
e.recordJobTimingWithStatus(startTime, time.Now(), j, Success, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) callJobWithRecover(j internalJob) (err error) {
|
||||
defer func() {
|
||||
if recoverData := recover(); recoverData != nil {
|
||||
_ = callJobFuncWithParams(j.afterJobRunsWithPanic, j.id, j.name, recoverData)
|
||||
|
||||
// if panic is occurred, we should return an error
|
||||
err = fmt.Errorf("%w from %v", ErrPanicRecovered, recoverData)
|
||||
}
|
||||
}()
|
||||
|
||||
return callJobFuncWithParams(j.function, j.parameters...)
|
||||
}
|
||||
|
||||
func (e *executor) recordJobTiming(start time.Time, end time.Time, j internalJob) {
|
||||
if e.monitor != nil {
|
||||
e.monitor.RecordJobTiming(start, end, j.id, j.name, j.tags)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) recordJobTimingWithStatus(start time.Time, end time.Time, j internalJob, status JobStatus, err error) {
|
||||
if e.monitorStatus != nil {
|
||||
e.monitorStatus.RecordJobTimingWithStatus(start, end, j.id, j.name, j.tags, status, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) incrementJobCounter(j internalJob, status JobStatus) {
|
||||
if e.monitor != nil {
|
||||
e.monitor.IncrementJob(j.id, j.name, j.tags, status)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) stop(standardJobsWg, singletonJobsWg, limitModeJobsWg *waitGroupWithMutex) {
|
||||
e.logger.Debug("gocron: stopping executor")
|
||||
// we've been asked to stop. This is either because the scheduler has been told
|
||||
// to stop all jobs or the scheduler has been asked to completely shutdown.
|
||||
//
|
||||
// cancel tells all the functions to stop their work and send in a done response
|
||||
e.cancel()
|
||||
|
||||
// the wait for job channels are used to report back whether we successfully waited
|
||||
// for all jobs to complete or if we hit the configured timeout.
|
||||
waitForJobs := make(chan struct{}, 1)
|
||||
waitForSingletons := make(chan struct{}, 1)
|
||||
waitForLimitMode := make(chan struct{}, 1)
|
||||
|
||||
// the waiter context is used to cancel the functions waiting on jobs.
|
||||
// this is done to avoid goroutine leaks.
|
||||
waiterCtx, waiterCancel := context.WithCancel(context.Background())
|
||||
|
||||
// wait for standard jobs to complete
|
||||
go func() {
|
||||
e.logger.Debug("gocron: waiting for standard jobs to complete")
|
||||
go func() {
|
||||
// this is done in a separate goroutine, so we aren't
|
||||
// blocked by the WaitGroup's Wait call in the event
|
||||
// that the waiter context is cancelled.
|
||||
// This particular goroutine could leak in the event that
|
||||
// some long-running standard job doesn't complete.
|
||||
standardJobsWg.Wait()
|
||||
e.logger.Debug("gocron: standard jobs completed")
|
||||
waitForJobs <- struct{}{}
|
||||
}()
|
||||
<-waiterCtx.Done()
|
||||
}()
|
||||
|
||||
// wait for per job singleton limit mode runner jobs to complete
|
||||
go func() {
|
||||
e.logger.Debug("gocron: waiting for singleton jobs to complete")
|
||||
go func() {
|
||||
singletonJobsWg.Wait()
|
||||
e.logger.Debug("gocron: singleton jobs completed")
|
||||
waitForSingletons <- struct{}{}
|
||||
}()
|
||||
<-waiterCtx.Done()
|
||||
}()
|
||||
|
||||
// wait for limit mode runners to complete
|
||||
go func() {
|
||||
e.logger.Debug("gocron: waiting for limit mode jobs to complete")
|
||||
go func() {
|
||||
limitModeJobsWg.Wait()
|
||||
e.logger.Debug("gocron: limitMode jobs completed")
|
||||
waitForLimitMode <- struct{}{}
|
||||
}()
|
||||
<-waiterCtx.Done()
|
||||
}()
|
||||
|
||||
// now either wait for all the jobs to complete,
|
||||
// or hit the timeout.
|
||||
var count int
|
||||
timeout := time.Now().Add(e.stopTimeout)
|
||||
for time.Now().Before(timeout) && count < 3 {
|
||||
select {
|
||||
case <-waitForJobs:
|
||||
count++
|
||||
case <-waitForSingletons:
|
||||
count++
|
||||
case <-waitForLimitMode:
|
||||
count++
|
||||
default:
|
||||
}
|
||||
}
|
||||
if count < 3 {
|
||||
e.done <- ErrStopJobsTimedOut
|
||||
e.logger.Debug("gocron: executor stopped - timed out")
|
||||
} else {
|
||||
e.done <- nil
|
||||
e.logger.Debug("gocron: executor stopped")
|
||||
}
|
||||
waiterCancel()
|
||||
|
||||
if e.limitMode != nil {
|
||||
e.limitMode.started = false
|
||||
}
|
||||
}
|
||||
1171
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
Normal file
1171
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
101
vendor/github.com/go-co-op/gocron/v2/logger.go
generated
vendored
Normal file
101
vendor/github.com/go-co-op/gocron/v2/logger.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
//go:generate mockgen -destination=mocks/logger.go -package=gocronmocks . Logger
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Logger is the interface that wraps the basic logging methods
|
||||
// used by gocron. The methods are modeled after the standard
|
||||
// library slog package. The default logger is a no-op logger.
|
||||
// To enable logging, use one of the provided New*Logger functions
|
||||
// or implement your own Logger. The actual level of Log that is logged
|
||||
// is handled by the implementation.
|
||||
type Logger interface {
|
||||
Debug(msg string, args ...any)
|
||||
Error(msg string, args ...any)
|
||||
Info(msg string, args ...any)
|
||||
Warn(msg string, args ...any)
|
||||
}
|
||||
|
||||
var _ Logger = (*noOpLogger)(nil)
|
||||
|
||||
type noOpLogger struct{}
|
||||
|
||||
func (l noOpLogger) Debug(_ string, _ ...any) {}
|
||||
func (l noOpLogger) Error(_ string, _ ...any) {}
|
||||
func (l noOpLogger) Info(_ string, _ ...any) {}
|
||||
func (l noOpLogger) Warn(_ string, _ ...any) {}
|
||||
|
||||
var _ Logger = (*logger)(nil)
|
||||
|
||||
// LogLevel is the level of logging that should be logged
|
||||
// when using the basic NewLogger.
|
||||
type LogLevel int
|
||||
|
||||
// The different log levels that can be used.
|
||||
const (
|
||||
LogLevelError LogLevel = iota
|
||||
LogLevelWarn
|
||||
LogLevelInfo
|
||||
LogLevelDebug
|
||||
)
|
||||
|
||||
type logger struct {
|
||||
log *log.Logger
|
||||
level LogLevel
|
||||
}
|
||||
|
||||
// NewLogger returns a new Logger that logs at the given level.
|
||||
func NewLogger(level LogLevel) Logger {
|
||||
l := log.New(os.Stdout, "", log.LstdFlags)
|
||||
return &logger{
|
||||
log: l,
|
||||
level: level,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Debug(msg string, args ...any) {
|
||||
if l.level < LogLevelDebug {
|
||||
return
|
||||
}
|
||||
l.log.Printf("DEBUG: %s%s\n", msg, logFormatArgs(args...))
|
||||
}
|
||||
|
||||
func (l *logger) Error(msg string, args ...any) {
|
||||
if l.level < LogLevelError {
|
||||
return
|
||||
}
|
||||
l.log.Printf("ERROR: %s%s\n", msg, logFormatArgs(args...))
|
||||
}
|
||||
|
||||
func (l *logger) Info(msg string, args ...any) {
|
||||
if l.level < LogLevelInfo {
|
||||
return
|
||||
}
|
||||
l.log.Printf("INFO: %s%s\n", msg, logFormatArgs(args...))
|
||||
}
|
||||
|
||||
func (l *logger) Warn(msg string, args ...any) {
|
||||
if l.level < LogLevelWarn {
|
||||
return
|
||||
}
|
||||
l.log.Printf("WARN: %s%s\n", msg, logFormatArgs(args...))
|
||||
}
|
||||
|
||||
func logFormatArgs(args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(args)%2 != 0 {
|
||||
return ", " + fmt.Sprint(args...)
|
||||
}
|
||||
var pairs []string
|
||||
for i := 0; i < len(args); i += 2 {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%v", args[i], args[i+1]))
|
||||
}
|
||||
return ", " + strings.Join(pairs, ", ")
|
||||
}
|
||||
36
vendor/github.com/go-co-op/gocron/v2/monitor.go
generated
vendored
Normal file
36
vendor/github.com/go-co-op/gocron/v2/monitor.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// JobStatus is the status of job run that should be collected with the metric.
|
||||
type JobStatus string
|
||||
|
||||
// The different statuses of job that can be used.
|
||||
const (
|
||||
Fail JobStatus = "fail"
|
||||
Success JobStatus = "success"
|
||||
Skip JobStatus = "skip"
|
||||
SingletonRescheduled JobStatus = "singleton_rescheduled"
|
||||
)
|
||||
|
||||
// Monitor represents the interface to collect jobs metrics.
|
||||
type Monitor interface {
|
||||
// IncrementJob will provide details about the job and expects the underlying implementation
|
||||
// to handle instantiating and incrementing a value
|
||||
IncrementJob(id uuid.UUID, name string, tags []string, status JobStatus)
|
||||
// RecordJobTiming will provide details about the job and the timing and expects the underlying implementation
|
||||
// to handle instantiating and recording the value
|
||||
RecordJobTiming(startTime, endTime time.Time, id uuid.UUID, name string, tags []string)
|
||||
}
|
||||
|
||||
// MonitorStatus extends RecordJobTiming with the job status.
|
||||
type MonitorStatus interface {
|
||||
Monitor
|
||||
// RecordJobTimingWithStatus will provide details about the job, its status, error and the timing and expects the underlying implementation
|
||||
// to handle instantiating and recording the value
|
||||
RecordJobTimingWithStatus(startTime, endTime time.Time, id uuid.UUID, name string, tags []string, status JobStatus, err error)
|
||||
}
|
||||
989
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
Normal file
989
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
Normal file
@@ -0,0 +1,989 @@
|
||||
//go:generate mockgen -destination=mocks/scheduler.go -package=gocronmocks . Scheduler
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var _ Scheduler = (*scheduler)(nil)
|
||||
|
||||
// Scheduler defines the interface for the Scheduler.
|
||||
type Scheduler interface {
|
||||
// Jobs returns all the jobs currently in the scheduler.
|
||||
Jobs() []Job
|
||||
// NewJob creates a new job in the Scheduler. The job is scheduled per the provided
|
||||
// definition when the Scheduler is started. If the Scheduler is already running
|
||||
// the job will be scheduled when the Scheduler is started.
|
||||
// If you set the first argument of your Task func to be a context.Context,
|
||||
// gocron will pass in a context (either the default Job context, or one
|
||||
// provided via WithContext) to the job and will cancel the context on shutdown.
|
||||
// This allows you to listen for and handle cancellation within your job.
|
||||
NewJob(JobDefinition, Task, ...JobOption) (Job, error)
|
||||
// RemoveByTags removes all jobs that have at least one of the provided tags.
|
||||
RemoveByTags(...string)
|
||||
// RemoveJob removes the job with the provided id.
|
||||
RemoveJob(uuid.UUID) error
|
||||
// Shutdown should be called when you no longer need
|
||||
// the Scheduler or Job's as the Scheduler cannot
|
||||
// be restarted after calling Shutdown. This is similar
|
||||
// to a Close or Cleanup method and is often deferred after
|
||||
// starting the scheduler.
|
||||
Shutdown() error
|
||||
// Start begins scheduling jobs for execution based
|
||||
// on each job's definition. Job's added to an already
|
||||
// running scheduler will be scheduled immediately based
|
||||
// on definition. Start is non-blocking.
|
||||
Start()
|
||||
// StopJobs stops the execution of all jobs in the scheduler.
|
||||
// This can be useful in situations where jobs need to be
|
||||
// paused globally and then restarted with Start().
|
||||
StopJobs() error
|
||||
// Update replaces the existing Job's JobDefinition with the provided
|
||||
// JobDefinition. The Job's Job.ID() remains the same.
|
||||
Update(uuid.UUID, JobDefinition, Task, ...JobOption) (Job, error)
|
||||
// JobsWaitingInQueue number of jobs waiting in Queue in case of LimitModeWait
|
||||
// In case of LimitModeReschedule or no limit it will be always zero
|
||||
JobsWaitingInQueue() int
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// ----------------- Scheduler -------------------
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
|
||||
type scheduler struct {
|
||||
// context used for shutting down
|
||||
shutdownCtx context.Context
|
||||
// cancel used to signal scheduler should shut down
|
||||
shutdownCancel context.CancelFunc
|
||||
// the executor, which actually runs the jobs sent to it via the scheduler
|
||||
exec executor
|
||||
// the map of jobs registered in the scheduler
|
||||
jobs map[uuid.UUID]internalJob
|
||||
// the location used by the scheduler for scheduling when relevant
|
||||
location *time.Location
|
||||
// whether the scheduler has been started or not
|
||||
started bool
|
||||
// globally applied JobOption's set on all jobs added to the scheduler
|
||||
// note: individually set JobOption's take precedence.
|
||||
globalJobOptions []JobOption
|
||||
// the scheduler's logger
|
||||
logger Logger
|
||||
|
||||
// used to tell the scheduler to start
|
||||
startCh chan struct{}
|
||||
// used to report that the scheduler has started
|
||||
startedCh chan struct{}
|
||||
// used to tell the scheduler to stop
|
||||
stopCh chan struct{}
|
||||
// used to report that the scheduler has stopped
|
||||
stopErrCh chan error
|
||||
// used to send all the jobs out when a request is made by the client
|
||||
allJobsOutRequest chan allJobsOutRequest
|
||||
// used to send a jobs out when a request is made by the client
|
||||
jobOutRequestCh chan jobOutRequest
|
||||
// used to run a job on-demand when requested by the client
|
||||
runJobRequestCh chan runJobRequest
|
||||
// new jobs are received here
|
||||
newJobCh chan newJobIn
|
||||
// requests from the client to remove jobs by ID are received here
|
||||
removeJobCh chan uuid.UUID
|
||||
// requests from the client to remove jobs by tags are received here
|
||||
removeJobsByTagsCh chan []string
|
||||
}
|
||||
|
||||
type newJobIn struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
job internalJob
|
||||
}
|
||||
|
||||
type jobOutRequest struct {
|
||||
id uuid.UUID
|
||||
outChan chan internalJob
|
||||
}
|
||||
|
||||
type runJobRequest struct {
|
||||
id uuid.UUID
|
||||
outChan chan error
|
||||
}
|
||||
|
||||
type allJobsOutRequest struct {
|
||||
outChan chan []Job
|
||||
}
|
||||
|
||||
// NewScheduler creates a new Scheduler instance.
|
||||
// The Scheduler is not started until Start() is called.
|
||||
//
|
||||
// NewJob will add jobs to the Scheduler, but they will not
|
||||
// be scheduled until Start() is called.
|
||||
func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
|
||||
schCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
exec := executor{
|
||||
stopCh: make(chan struct{}),
|
||||
stopTimeout: time.Second * 10,
|
||||
singletonRunners: nil,
|
||||
logger: &noOpLogger{},
|
||||
clock: clockwork.NewRealClock(),
|
||||
|
||||
jobsIn: make(chan jobIn),
|
||||
jobsOutForRescheduling: make(chan uuid.UUID),
|
||||
jobsOutCompleted: make(chan uuid.UUID),
|
||||
jobOutRequest: make(chan jobOutRequest, 1000),
|
||||
done: make(chan error),
|
||||
}
|
||||
|
||||
s := &scheduler{
|
||||
shutdownCtx: schCtx,
|
||||
shutdownCancel: cancel,
|
||||
exec: exec,
|
||||
jobs: make(map[uuid.UUID]internalJob),
|
||||
location: time.Local,
|
||||
logger: &noOpLogger{},
|
||||
|
||||
newJobCh: make(chan newJobIn),
|
||||
removeJobCh: make(chan uuid.UUID),
|
||||
removeJobsByTagsCh: make(chan []string),
|
||||
startCh: make(chan struct{}),
|
||||
startedCh: make(chan struct{}),
|
||||
stopCh: make(chan struct{}),
|
||||
stopErrCh: make(chan error, 1),
|
||||
jobOutRequestCh: make(chan jobOutRequest),
|
||||
runJobRequestCh: make(chan runJobRequest),
|
||||
allJobsOutRequest: make(chan allJobsOutRequest),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
err := option(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.logger.Info("gocron: new scheduler created")
|
||||
for {
|
||||
select {
|
||||
case id := <-s.exec.jobsOutForRescheduling:
|
||||
s.selectExecJobsOutForRescheduling(id)
|
||||
|
||||
case id := <-s.exec.jobsOutCompleted:
|
||||
s.selectExecJobsOutCompleted(id)
|
||||
|
||||
case in := <-s.newJobCh:
|
||||
s.selectNewJob(in)
|
||||
|
||||
case id := <-s.removeJobCh:
|
||||
s.selectRemoveJob(id)
|
||||
|
||||
case tags := <-s.removeJobsByTagsCh:
|
||||
s.selectRemoveJobsByTags(tags)
|
||||
|
||||
case out := <-s.exec.jobOutRequest:
|
||||
s.selectJobOutRequest(out)
|
||||
|
||||
case out := <-s.jobOutRequestCh:
|
||||
s.selectJobOutRequest(out)
|
||||
|
||||
case out := <-s.allJobsOutRequest:
|
||||
s.selectAllJobsOutRequest(out)
|
||||
|
||||
case run := <-s.runJobRequestCh:
|
||||
s.selectRunJobRequest(run)
|
||||
|
||||
case <-s.startCh:
|
||||
s.selectStart()
|
||||
|
||||
case <-s.stopCh:
|
||||
s.stopScheduler()
|
||||
|
||||
case <-s.shutdownCtx.Done():
|
||||
s.stopScheduler()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// --------- Scheduler Channel Methods -----------
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
|
||||
// The scheduler's channel functions are broken out here
|
||||
// to allow prioritizing within the select blocks. The idea
|
||||
// being that we want to make sure that scheduling tasks
|
||||
// are not blocked by requests from the caller for information
|
||||
// about jobs.
|
||||
|
||||
func (s *scheduler) stopScheduler() {
|
||||
s.logger.Debug("gocron: stopping scheduler")
|
||||
if s.started {
|
||||
s.exec.stopCh <- struct{}{}
|
||||
}
|
||||
|
||||
for _, j := range s.jobs {
|
||||
j.stop()
|
||||
}
|
||||
for id, j := range s.jobs {
|
||||
<-j.ctx.Done()
|
||||
|
||||
j.ctx, j.cancel = context.WithCancel(s.shutdownCtx)
|
||||
s.jobs[id] = j
|
||||
}
|
||||
var err error
|
||||
if s.started {
|
||||
t := time.NewTimer(s.exec.stopTimeout + 1*time.Second)
|
||||
select {
|
||||
case err = <-s.exec.done:
|
||||
t.Stop()
|
||||
case <-t.C:
|
||||
err = ErrStopExecutorTimedOut
|
||||
}
|
||||
}
|
||||
s.stopErrCh <- err
|
||||
s.started = false
|
||||
s.logger.Debug("gocron: scheduler stopped")
|
||||
}
|
||||
|
||||
func (s *scheduler) selectAllJobsOutRequest(out allJobsOutRequest) {
|
||||
outJobs := make([]Job, len(s.jobs))
|
||||
var counter int
|
||||
for _, j := range s.jobs {
|
||||
outJobs[counter] = s.jobFromInternalJob(j)
|
||||
counter++
|
||||
}
|
||||
slices.SortFunc(outJobs, func(a, b Job) int {
|
||||
aID, bID := a.ID().String(), b.ID().String()
|
||||
switch {
|
||||
case aID < bID:
|
||||
return -1
|
||||
case aID > bID:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
})
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case out.outChan <- outJobs:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) selectRunJobRequest(run runJobRequest) {
|
||||
j, ok := s.jobs[run.id]
|
||||
if !ok {
|
||||
select {
|
||||
case run.outChan <- ErrJobNotFound:
|
||||
default:
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
select {
|
||||
case run.outChan <- ErrJobRunNowFailed:
|
||||
default:
|
||||
}
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: j.id,
|
||||
shouldSendOut: false,
|
||||
}:
|
||||
select {
|
||||
case run.outChan <- nil:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) selectRemoveJob(id uuid.UUID) {
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
j.stop()
|
||||
delete(s.jobs, id)
|
||||
}
|
||||
|
||||
// Jobs coming back from the executor to the scheduler that
|
||||
// need to be evaluated for rescheduling.
|
||||
func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
// the job was removed while it was running, and
|
||||
// so we don't need to reschedule it.
|
||||
return
|
||||
}
|
||||
|
||||
if j.stopTimeReached(s.now()) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleFrom := j.lastRun
|
||||
if len(j.nextScheduled) > 0 {
|
||||
// always grab the last element in the slice as that is the furthest
|
||||
// out in the future and the time from which we want to calculate
|
||||
// the subsequent next run time.
|
||||
slices.SortStableFunc(j.nextScheduled, ascendingTime)
|
||||
scheduleFrom = j.nextScheduled[len(j.nextScheduled)-1]
|
||||
}
|
||||
|
||||
if scheduleFrom.IsZero() {
|
||||
scheduleFrom = j.startTime
|
||||
}
|
||||
|
||||
next := j.next(scheduleFrom)
|
||||
if next.IsZero() {
|
||||
// the job's next function will return zero for OneTime jobs.
|
||||
// since they are one time only, they do not need rescheduling.
|
||||
return
|
||||
}
|
||||
|
||||
if next.Before(s.now()) {
|
||||
// in some cases the next run time can be in the past, for example:
|
||||
// - the time on the machine was incorrect and has been synced with ntp
|
||||
// - the machine went to sleep, and woke up some time later
|
||||
// in those cases, we want to increment to the next run in the future
|
||||
// and schedule the job for that time.
|
||||
for next.Before(s.now()) {
|
||||
next = j.next(next)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing timer to prevent leaks
|
||||
if j.timer != nil {
|
||||
j.timer.Stop()
|
||||
j.timer = nil // Ensure timer is cleared for GC
|
||||
}
|
||||
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
// set the actual timer on the job here and listen for
|
||||
// shut down events so that the job doesn't attempt to
|
||||
// run if the scheduler has been shutdown.
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: j.id,
|
||||
shouldSendOut: true,
|
||||
}:
|
||||
}
|
||||
})
|
||||
// update the job with its new next and last run times and timer.
|
||||
s.jobs[id] = j
|
||||
}
|
||||
|
||||
func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) {
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// if the job has nextScheduled time in the past,
|
||||
// we need to remove any that are in the past.
|
||||
var newNextScheduled []time.Time
|
||||
for _, t := range j.nextScheduled {
|
||||
if t.Before(s.now()) {
|
||||
continue
|
||||
}
|
||||
newNextScheduled = append(newNextScheduled, t)
|
||||
}
|
||||
j.nextScheduled = newNextScheduled
|
||||
|
||||
// if the job has a limited number of runs set, we need to
|
||||
// check how many runs have occurred and stop running this
|
||||
// job if it has reached the limit.
|
||||
if j.limitRunsTo != nil {
|
||||
j.limitRunsTo.runCount = j.limitRunsTo.runCount + 1
|
||||
if j.limitRunsTo.runCount == j.limitRunsTo.limit {
|
||||
go func() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return
|
||||
case s.removeJobCh <- id:
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
j.lastRun = s.now()
|
||||
s.jobs[id] = j
|
||||
}
|
||||
|
||||
func (s *scheduler) selectJobOutRequest(out jobOutRequest) {
|
||||
if j, ok := s.jobs[out.id]; ok {
|
||||
select {
|
||||
case out.outChan <- j:
|
||||
case <-s.shutdownCtx.Done():
|
||||
}
|
||||
}
|
||||
close(out.outChan)
|
||||
}
|
||||
|
||||
func (s *scheduler) selectNewJob(in newJobIn) {
|
||||
j := in.job
|
||||
if s.started {
|
||||
next := j.startTime
|
||||
if j.startImmediately {
|
||||
next = s.now()
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: j.id,
|
||||
shouldSendOut: true,
|
||||
}:
|
||||
}
|
||||
} else {
|
||||
if next.IsZero() {
|
||||
next = j.next(s.now())
|
||||
}
|
||||
|
||||
id := j.id
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: id,
|
||||
shouldSendOut: true,
|
||||
}:
|
||||
}
|
||||
})
|
||||
}
|
||||
j.startTime = next
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
}
|
||||
|
||||
s.jobs[j.id] = j
|
||||
in.cancel()
|
||||
}
|
||||
|
||||
func (s *scheduler) selectRemoveJobsByTags(tags []string) {
|
||||
for _, j := range s.jobs {
|
||||
for _, tag := range tags {
|
||||
if slices.Contains(j.tags, tag) {
|
||||
j.stop()
|
||||
delete(s.jobs, j.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) selectStart() {
|
||||
s.logger.Debug("gocron: scheduler starting")
|
||||
go s.exec.start()
|
||||
|
||||
s.started = true
|
||||
for id, j := range s.jobs {
|
||||
next := j.startTime
|
||||
if j.startImmediately {
|
||||
next = s.now()
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: id,
|
||||
shouldSendOut: true,
|
||||
}:
|
||||
}
|
||||
} else {
|
||||
if next.IsZero() {
|
||||
next = j.next(s.now())
|
||||
}
|
||||
|
||||
jobID := id
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
id: jobID,
|
||||
shouldSendOut: true,
|
||||
}:
|
||||
}
|
||||
})
|
||||
}
|
||||
j.startTime = next
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
s.jobs[id] = j
|
||||
}
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.startedCh <- struct{}{}:
|
||||
s.logger.Info("gocron: scheduler started")
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// ------------- Scheduler Methods ---------------
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
|
||||
func (s *scheduler) now() time.Time {
|
||||
return s.exec.clock.Now().In(s.location)
|
||||
}
|
||||
|
||||
func (s *scheduler) jobFromInternalJob(in internalJob) job {
|
||||
return job{
|
||||
in.id,
|
||||
in.name,
|
||||
slices.Clone(in.tags),
|
||||
s.jobOutRequestCh,
|
||||
s.runJobRequestCh,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) Jobs() []Job {
|
||||
outChan := make(chan []Job)
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.allJobsOutRequest <- allJobsOutRequest{outChan: outChan}:
|
||||
}
|
||||
|
||||
var jobs []Job
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case jobs = <-outChan:
|
||||
}
|
||||
|
||||
return jobs
|
||||
}
|
||||
|
||||
func (s *scheduler) NewJob(jobDefinition JobDefinition, task Task, options ...JobOption) (Job, error) {
|
||||
return s.addOrUpdateJob(uuid.Nil, jobDefinition, task, options)
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyInterfaceVariadic(taskFunc reflect.Value, tsk task, variadicStart int) error {
|
||||
ifaceType := taskFunc.Type().In(variadicStart).Elem()
|
||||
for i := variadicStart; i < len(tsk.parameters); i++ {
|
||||
if !reflect.TypeOf(tsk.parameters[i]).Implements(ifaceType) {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyVariadic(taskFunc reflect.Value, tsk task, variadicStart int) error {
|
||||
if err := s.verifyNonVariadic(taskFunc, tsk, variadicStart); err != nil {
|
||||
return err
|
||||
}
|
||||
parameterType := taskFunc.Type().In(variadicStart).Elem().Kind()
|
||||
if parameterType == reflect.Interface {
|
||||
return s.verifyInterfaceVariadic(taskFunc, tsk, variadicStart)
|
||||
}
|
||||
if parameterType == reflect.Pointer {
|
||||
parameterType = reflect.Indirect(reflect.ValueOf(taskFunc.Type().In(variadicStart))).Kind()
|
||||
}
|
||||
|
||||
for i := variadicStart; i < len(tsk.parameters); i++ {
|
||||
argumentType := reflect.TypeOf(tsk.parameters[i]).Kind()
|
||||
if argumentType == reflect.Interface || argumentType == reflect.Pointer {
|
||||
argumentType = reflect.TypeOf(tsk.parameters[i]).Elem().Kind()
|
||||
}
|
||||
if argumentType != parameterType {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyNonVariadic(taskFunc reflect.Value, tsk task, length int) error {
|
||||
for i := 0; i < length; i++ {
|
||||
t1 := reflect.TypeOf(tsk.parameters[i]).Kind()
|
||||
if t1 == reflect.Interface || t1 == reflect.Pointer {
|
||||
t1 = reflect.TypeOf(tsk.parameters[i]).Elem().Kind()
|
||||
}
|
||||
t2 := reflect.New(taskFunc.Type().In(i)).Elem().Kind()
|
||||
if t2 == reflect.Interface || t2 == reflect.Pointer {
|
||||
t2 = reflect.Indirect(reflect.ValueOf(taskFunc.Type().In(i))).Kind()
|
||||
}
|
||||
if t1 != t2 {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyParameterType(taskFunc reflect.Value, tsk task) error {
|
||||
isVariadic := taskFunc.Type().IsVariadic()
|
||||
if isVariadic {
|
||||
variadicStart := taskFunc.Type().NumIn() - 1
|
||||
return s.verifyVariadic(taskFunc, tsk, variadicStart)
|
||||
}
|
||||
expectedParameterLength := taskFunc.Type().NumIn()
|
||||
if len(tsk.parameters) != expectedParameterLength {
|
||||
return ErrNewJobWrongNumberOfParameters
|
||||
}
|
||||
return s.verifyNonVariadic(taskFunc, tsk, expectedParameterLength)
|
||||
}
|
||||
|
||||
func (s *scheduler) addOrUpdateJob(id uuid.UUID, definition JobDefinition, taskWrapper Task, options []JobOption) (Job, error) {
|
||||
j := internalJob{}
|
||||
if id == uuid.Nil {
|
||||
j.id = uuid.New()
|
||||
} else {
|
||||
currentJob := requestJobCtx(s.shutdownCtx, id, s.jobOutRequestCh)
|
||||
if currentJob != nil && currentJob.id != uuid.Nil {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return nil, nil
|
||||
case s.removeJobCh <- id:
|
||||
<-currentJob.ctx.Done()
|
||||
}
|
||||
}
|
||||
|
||||
j.id = id
|
||||
}
|
||||
|
||||
if taskWrapper == nil {
|
||||
return nil, ErrNewJobTaskNil
|
||||
}
|
||||
|
||||
tsk := taskWrapper()
|
||||
taskFunc := reflect.ValueOf(tsk.function)
|
||||
for taskFunc.Kind() == reflect.Ptr {
|
||||
taskFunc = taskFunc.Elem()
|
||||
}
|
||||
|
||||
if taskFunc.Kind() != reflect.Func {
|
||||
return nil, ErrNewJobTaskNotFunc
|
||||
}
|
||||
|
||||
j.name = runtime.FuncForPC(taskFunc.Pointer()).Name()
|
||||
j.function = tsk.function
|
||||
j.parameters = tsk.parameters
|
||||
|
||||
// apply global job options
|
||||
for _, option := range s.globalJobOptions {
|
||||
if err := option(&j, s.now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// apply job specific options, which take precedence
|
||||
for _, option := range options {
|
||||
if err := option(&j, s.now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if j.parentCtx == nil {
|
||||
j.parentCtx = s.shutdownCtx
|
||||
}
|
||||
j.ctx, j.cancel = context.WithCancel(j.parentCtx)
|
||||
|
||||
if !taskFunc.IsZero() && taskFunc.Type().NumIn() > 0 {
|
||||
// if the first parameter is a context.Context and params have no context.Context, add current ctx to the params
|
||||
if taskFunc.Type().In(0) == reflect.TypeOf((*context.Context)(nil)).Elem() {
|
||||
if len(tsk.parameters) == 0 {
|
||||
tsk.parameters = []any{j.ctx}
|
||||
j.parameters = []any{j.ctx}
|
||||
} else if _, ok := tsk.parameters[0].(context.Context); !ok {
|
||||
tsk.parameters = append([]any{j.ctx}, tsk.parameters...)
|
||||
j.parameters = append([]any{j.ctx}, j.parameters...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.verifyParameterType(taskFunc, tsk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := definition.setup(&j, s.location, s.exec.clock.Now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newJobCtx, newJobCancel := context.WithCancel(context.Background())
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.newJobCh <- newJobIn{
|
||||
ctx: newJobCtx,
|
||||
cancel: newJobCancel,
|
||||
job: j,
|
||||
}:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-newJobCtx.Done():
|
||||
case <-s.shutdownCtx.Done():
|
||||
}
|
||||
|
||||
out := s.jobFromInternalJob(j)
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *scheduler) RemoveByTags(tags ...string) {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.removeJobsByTagsCh <- tags:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) RemoveJob(id uuid.UUID) error {
|
||||
j := requestJobCtx(s.shutdownCtx, id, s.jobOutRequestCh)
|
||||
if j == nil || j.id == uuid.Nil {
|
||||
return ErrJobNotFound
|
||||
}
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.removeJobCh <- id:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) Start() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.startCh <- struct{}{}:
|
||||
<-s.startedCh
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) StopJobs() error {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return nil
|
||||
case s.stopCh <- struct{}{}:
|
||||
}
|
||||
|
||||
t := time.NewTimer(s.exec.stopTimeout + 2*time.Second)
|
||||
select {
|
||||
case err := <-s.stopErrCh:
|
||||
t.Stop()
|
||||
return err
|
||||
case <-t.C:
|
||||
return ErrStopSchedulerTimedOut
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) Shutdown() error {
|
||||
s.shutdownCancel()
|
||||
|
||||
t := time.NewTimer(s.exec.stopTimeout + 2*time.Second)
|
||||
select {
|
||||
case err := <-s.stopErrCh:
|
||||
|
||||
t.Stop()
|
||||
return err
|
||||
case <-t.C:
|
||||
return ErrStopSchedulerTimedOut
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scheduler) Update(id uuid.UUID, jobDefinition JobDefinition, task Task, options ...JobOption) (Job, error) {
|
||||
return s.addOrUpdateJob(id, jobDefinition, task, options)
|
||||
}
|
||||
|
||||
func (s *scheduler) JobsWaitingInQueue() int {
|
||||
if s.exec.limitMode != nil && s.exec.limitMode.mode == LimitModeWait {
|
||||
return len(s.exec.limitMode.in)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// ------------- Scheduler Options ---------------
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
|
||||
// SchedulerOption defines the function for setting
|
||||
// options on the Scheduler.
|
||||
type SchedulerOption func(*scheduler) error
|
||||
|
||||
// WithClock sets the clock used by the Scheduler
|
||||
// to the clock provided. See https://github.com/jonboulle/clockwork
|
||||
func WithClock(clock clockwork.Clock) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if clock == nil {
|
||||
return ErrWithClockNil
|
||||
}
|
||||
s.exec.clock = clock
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDistributedElector sets the elector to be used by multiple
|
||||
// Scheduler instances to determine who should be the leader.
|
||||
// Only the leader runs jobs, while non-leaders wait and continue
|
||||
// to check if a new leader has been elected.
|
||||
func WithDistributedElector(elector Elector) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if elector == nil {
|
||||
return ErrWithDistributedElectorNil
|
||||
}
|
||||
s.exec.elector = elector
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDistributedLocker sets the locker to be used by multiple
|
||||
// Scheduler instances to ensure that only one instance of each
|
||||
// job is run.
|
||||
// To disable this global locker for specific jobs, see
|
||||
// WithDisabledDistributedJobLocker.
|
||||
func WithDistributedLocker(locker Locker) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if locker == nil {
|
||||
return ErrWithDistributedLockerNil
|
||||
}
|
||||
s.exec.locker = locker
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithGlobalJobOptions sets JobOption's that will be applied to
|
||||
// all jobs added to the scheduler. JobOption's set on the job
|
||||
// itself will override if the same JobOption is set globally.
|
||||
func WithGlobalJobOptions(jobOptions ...JobOption) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
s.globalJobOptions = jobOptions
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LimitMode defines the modes used for handling jobs that reach
|
||||
// the limit provided in WithLimitConcurrentJobs
|
||||
type LimitMode int
|
||||
|
||||
const (
|
||||
// LimitModeReschedule causes jobs reaching the limit set in
|
||||
// WithLimitConcurrentJobs or WithSingletonMode to be skipped
|
||||
// and rescheduled for the next run time rather than being
|
||||
// queued up to wait.
|
||||
LimitModeReschedule = 1
|
||||
|
||||
// LimitModeWait causes jobs reaching the limit set in
|
||||
// WithLimitConcurrentJobs or WithSingletonMode to wait
|
||||
// in a queue until a slot becomes available to run.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Warning: do not use this mode if your jobs will continue to stack
|
||||
// up beyond the ability of the limit workers to keep up. An example of
|
||||
// what NOT to do:
|
||||
//
|
||||
// s, _ := gocron.NewScheduler(gocron.WithLimitConcurrentJobs)
|
||||
// s.NewJob(
|
||||
// gocron.DurationJob(
|
||||
// time.Second,
|
||||
// Task{
|
||||
// Function: func() {
|
||||
// time.Sleep(10 * time.Second)
|
||||
// },
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
LimitModeWait = 2
|
||||
)
|
||||
|
||||
// WithLimitConcurrentJobs sets the limit and mode to be used by the
|
||||
// Scheduler for limiting the number of jobs that may be running at
|
||||
// a given time.
|
||||
//
|
||||
// Note: the limit mode selected for WithLimitConcurrentJobs takes initial
|
||||
// precedence in the event you are also running a limit mode at the job level
|
||||
// using WithSingletonMode.
|
||||
//
|
||||
// Warning: a single time consuming job can dominate your limit in the event
|
||||
// you are running both the scheduler limit WithLimitConcurrentJobs(1, LimitModeWait)
|
||||
// and a job limit WithSingletonMode(LimitModeReschedule).
|
||||
func WithLimitConcurrentJobs(limit uint, mode LimitMode) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if limit == 0 {
|
||||
return ErrWithLimitConcurrentJobsZero
|
||||
}
|
||||
s.exec.limitMode = &limitModeConfig{
|
||||
mode: mode,
|
||||
limit: limit,
|
||||
in: make(chan jobIn, 1000),
|
||||
singletonJobs: make(map[uuid.UUID]struct{}),
|
||||
}
|
||||
if mode == LimitModeReschedule {
|
||||
s.exec.limitMode.rescheduleLimiter = make(chan struct{}, limit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLocation sets the location (i.e. timezone) that the scheduler
|
||||
// should operate within. In many systems time.Local is UTC.
|
||||
// Default: time.Local
|
||||
func WithLocation(location *time.Location) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if location == nil {
|
||||
return ErrWithLocationNil
|
||||
}
|
||||
s.location = location
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets the logger to be used by the Scheduler.
|
||||
func WithLogger(logger Logger) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if logger == nil {
|
||||
return ErrWithLoggerNil
|
||||
}
|
||||
s.logger = logger
|
||||
s.exec.logger = logger
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStopTimeout sets the amount of time the Scheduler should
|
||||
// wait gracefully for jobs to complete before returning when
|
||||
// StopJobs() or Shutdown() are called.
|
||||
// Default: 10 * time.Second
|
||||
func WithStopTimeout(timeout time.Duration) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if timeout <= 0 {
|
||||
return ErrWithStopTimeoutZeroOrNegative
|
||||
}
|
||||
s.exec.stopTimeout = timeout
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitor sets the metrics provider to be used by the Scheduler.
|
||||
func WithMonitor(monitor Monitor) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if monitor == nil {
|
||||
return ErrWithMonitorNil
|
||||
}
|
||||
s.exec.monitor = monitor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitorStatus sets the metrics provider to be used by the Scheduler.
|
||||
func WithMonitorStatus(monitor MonitorStatus) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if monitor == nil {
|
||||
return ErrWithMonitorNil
|
||||
}
|
||||
s.exec.monitorStatus = monitor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
118
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
Normal file
118
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
package gocron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func callJobFuncWithParams(jobFunc any, params ...any) error {
|
||||
if jobFunc == nil {
|
||||
return nil
|
||||
}
|
||||
f := reflect.ValueOf(jobFunc)
|
||||
if f.IsZero() {
|
||||
return nil
|
||||
}
|
||||
if len(params) != f.Type().NumIn() {
|
||||
return nil
|
||||
}
|
||||
in := make([]reflect.Value, len(params))
|
||||
for k, param := range params {
|
||||
in[k] = reflect.ValueOf(param)
|
||||
}
|
||||
returnValues := f.Call(in)
|
||||
for _, val := range returnValues {
|
||||
i := val.Interface()
|
||||
if err, ok := i.(error); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func requestJob(id uuid.UUID, ch chan jobOutRequest) *internalJob {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
return requestJobCtx(ctx, id, ch)
|
||||
}
|
||||
|
||||
func requestJobCtx(ctx context.Context, id uuid.UUID, ch chan jobOutRequest) *internalJob {
|
||||
resp := make(chan internalJob, 1)
|
||||
select {
|
||||
case ch <- jobOutRequest{
|
||||
id: id,
|
||||
outChan: resp,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
var j internalJob
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case jobReceived := <-resp:
|
||||
j = jobReceived
|
||||
}
|
||||
return &j
|
||||
}
|
||||
|
||||
func removeSliceDuplicatesInt(in []int) []int {
|
||||
m := make(map[int]struct{})
|
||||
|
||||
for _, i := range in {
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
return maps.Keys(m)
|
||||
}
|
||||
|
||||
func convertAtTimesToDateTime(atTimes AtTimes, location *time.Location) ([]time.Time, error) {
|
||||
if atTimes == nil {
|
||||
return nil, errAtTimesNil
|
||||
}
|
||||
var atTimesDate []time.Time
|
||||
for _, a := range atTimes() {
|
||||
if a == nil {
|
||||
return nil, errAtTimeNil
|
||||
}
|
||||
at := a()
|
||||
if at.hours > 23 {
|
||||
return nil, errAtTimeHours
|
||||
} else if at.minutes > 59 || at.seconds > 59 {
|
||||
return nil, errAtTimeMinSec
|
||||
}
|
||||
atTimesDate = append(atTimesDate, at.time(location))
|
||||
}
|
||||
slices.SortStableFunc(atTimesDate, ascendingTime)
|
||||
return atTimesDate, nil
|
||||
}
|
||||
|
||||
func ascendingTime(a, b time.Time) int {
|
||||
return a.Compare(b)
|
||||
}
|
||||
|
||||
type waitGroupWithMutex struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *waitGroupWithMutex) Add(delta int) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.wg.Add(delta)
|
||||
}
|
||||
|
||||
func (w *waitGroupWithMutex) Done() {
|
||||
w.wg.Done()
|
||||
}
|
||||
|
||||
func (w *waitGroupWithMutex) Wait() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.wg.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user