diff --git a/go.mod b/go.mod index d3155cc9a..36b3a063c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.8.2 - github.com/go-co-op/gocron v1.13.0 + github.com/go-co-op/gocron v1.31.0 github.com/imdario/mergo v0.3.12 github.com/json-iterator/go v1.1.12 // indirect github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.1.1-0.20210510153419-66a699ae3b05 @@ -23,7 +23,7 @@ require ( require ( github.com/robfig/cron/v3 v3.0.1 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + go.uber.org/atomic v1.9.0 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect ) diff --git a/go.sum b/go.sum index 9e8d7ca65..7a3496a53 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-co-op/gocron v1.13.0 h1:BjkuNImPy5NuIPEifhWItFG7pYyr27cyjS6BN9w/D4c= -github.com/go-co-op/gocron v1.13.0/go.mod h1:GD5EIEly1YNW+LovFVx5dzbYVcIc8544K99D8UVRpGo= +github.com/go-co-op/gocron v1.31.0 h1:8VaWk7ARDpsVYFP8SmjvHrBZQkcPQ7HyAzF7acG57yE= +github.com/go-co-op/gocron v1.31.0/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= 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= @@ -271,6 +271,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -297,7 +300,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -319,6 +321,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -328,6 +331,9 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -338,12 +344,17 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -363,6 +374,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -485,8 +498,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -738,8 +749,9 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= diff --git a/vendor/github.com/go-co-op/gocron/.golangci.yaml b/vendor/github.com/go-co-op/gocron/.golangci.yaml index 611fb3659..03545280e 100644 --- a/vendor/github.com/go-co-op/gocron/.golangci.yaml +++ b/vendor/github.com/go-co-op/gocron/.golangci.yaml @@ -1,5 +1,5 @@ run: - timeout: 2m + timeout: 5m issues-exit-code: 1 tests: true @@ -15,20 +15,19 @@ issues: linters: enable: - bodyclose - - deadcode - - errcheck - - gofmt - - revive + - exportloopref + - gofumpt + - goimports - gosec - gosimple - govet - ineffassign - misspell + - revive - staticcheck - - structcheck - typecheck - unused - - varcheck + - whitespace output: # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" @@ -47,3 +46,5 @@ output: linters-settings: golint: min-confidence: 0.8 + +fix: true diff --git a/vendor/github.com/go-co-op/gocron/Makefile b/vendor/github.com/go-co-op/gocron/Makefile index 08cbf82b9..1e16aef6e 100644 --- a/vendor/github.com/go-co-op/gocron/Makefile +++ b/vendor/github.com/go-co-op/gocron/Makefile @@ -5,29 +5,9 @@ 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) + @grep "^func " example_test.go | sort -c + @golangci-lint run 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 7805fc0a2..59f491906 100644 --- a/vendor/github.com/go-co-op/gocron/README.md +++ b/vendor/github.com/go-co-op/gocron/README.md @@ -1,30 +1,43 @@ # 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"lint") ![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) +[![CI State](https://github.com/go-co-op/gocron/actions/workflows/go_test.yml/badge.svg?branch=main&event=push)](https://github.com/go-co-op/gocron/actions) +![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 job scheduling package which lets you run Go functions at pre-determined intervals using a simple, human-friendly syntax. +gocron is a job scheduling package which lets you run Go functions at pre-determined intervals +using a simple, human-friendly syntax. -gocron is a Golang scheduler implementation similar to the Ruby module [clockwork](https://github.com/tomykaira/clockwork) and the Python job scheduling package [schedule](https://github.com/dbader/schedule). +gocron is a Golang scheduler implementation similar to 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 that were used for design input: - [Rethinking Cron](http://adam.herokuapp.com/past/2010/4/13/rethinking_cron/) - [Replace Cron with Clockwork](http://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/) -If you want to chat, you can find us at Slack! [](https://gophers.slack.com/archives/CQ7T0T1FW) +If you want to chat, you can find us at Slack! +[](https://gophers.slack.com/archives/CQ7T0T1FW) ## Concepts -- **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. +- **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. ## Examples ```golang s := gocron.NewScheduler(time.UTC) -s.Every(5).Seconds().Do(func(){ ... }) +// Every starts the job immediately and then runs at the +// specified interval +job, err := s.Every(5).Seconds().Do(func(){ ... }) +if err != nil { + // handle the error related to setting up the job +} // strings parse to duration s.Every("5m").Do(func(){ ... }) @@ -50,6 +63,9 @@ s.Every(2).MonthLastDay().Do(func(){ ... }) // cron expressions supported s.Cron("*/1 * * * *").Do(task) // every minute +// cron second-level expressions supported +s.CronWithSeconds("*/1 * * * * *").Do(task) // every second + // you can start running the scheduler in two different ways: // starts the scheduler asynchronously s.StartAsync() @@ -74,11 +90,16 @@ For more examples, take a look in our [go docs](https://pkg.go.dev/github.com/go There are several options available to restrict how jobs run: -| 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 | +| 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 | +| Distributed locking | `WithDistributedLocker()` | prevents the same job from being run more than once when running multiple instances of the scheduler | + +## Distributed Locker Implementations + +- Redis: [redislock](https://github.com/go-co-op/gocron-redis-lock) `go get github.com/go-co-op/gocron-redis-lock` ## Tags @@ -105,10 +126,56 @@ s.RunByTag("tag") ## FAQ - Q: I'm running multiple pods on a distributed environment. How can I make a job not run once per pod causing duplication? - - A: We recommend using your own lock solution within the jobs themselves (you could use [Redis](https://redis.io/topics/distlock), for example) + - We recommend using your own lock solution within the jobs themselves (you could use [Redis](https://redis.io/topics/distlock), for example) + - A2: Use the scheduler option `WithDistributedLocker` and either use an implemented [backend](#distributed-locker-implementations) + or implement your own and contribute it back in a PR! - 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()`. + - A2: You can listen to the job context Done channel to know when the job has been canceled + ```golang + task := func(in string, job gocron.Job) { + fmt.Printf("this job's last run: %s this job's next run: %s\n", job.LastRun(), job.NextRun()) + fmt.Printf("in argument is %s\n", in) + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-job.Context().Done(): + fmt.Printf("function has been canceled, performing cleanup and exiting gracefully\n") + return + case <-ticker.C: + fmt.Printf("performing a hard job that takes a long time that I want to kill whenever I want\n") + } + } + } + + var err error + s := gocron.NewScheduler(time.UTC) + s.SingletonModeAll() + j, err := s.Every(1).Hour().Tag("myJob").DoWithJobDetails(task, "foo") + if err != nil { + log.Fatalln("error scheduling job", err) + } + + s.StartAsync() + + // Simulate some more work + time.Sleep(time.Second) + + // I want to stop the job, together with the underlying goroutine + fmt.Printf("now I want to kill the job\n") + err = s.RemoveByTag("myJob") + if err != nil { + log.Fatalln("error removing job by tag", err) + } + + // Wait a bit so that we can see that the job is exiting gracefully + time.Sleep(time.Second) + fmt.Printf("Job: %#v, Error: %#v", j, err) + ``` --- @@ -130,3 +197,9 @@ Looking to contribute? Try to follow these guidelines: ![design-diagram](https://user-images.githubusercontent.com/19351306/110375142-2ba88680-8017-11eb-80c3-554cc746b165.png) [Jetbrains](https://www.jetbrains.com/?from=gocron) supports this project with GoLand licenses. We appreciate their support for free and open source software! + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=go-co-op/gocron&type=Date)](https://star-history.com/#go-co-op/gocron&Date) + + diff --git a/vendor/github.com/go-co-op/gocron/SECURITY.md b/vendor/github.com/go-co-op/gocron/SECURITY.md index 6943a66f5..6b9864123 100644 --- a/vendor/github.com/go-co-op/gocron/SECURITY.md +++ b/vendor/github.com/go-co-op/gocron/SECURITY.md @@ -12,4 +12,4 @@ The current plan is to maintain version 1 as long as possible incorporating any Vulnerabilities can be reported by [opening an issue](https://github.com/go-co-op/gocron/issues/new/choose) or reaching out on Slack: [](https://gophers.slack.com/archives/CQ7T0T1FW) -We will do our best to addrerss any vulnerabilites in an expeditious manner. +We will do our best to addrerss any vulnerabilities in an expeditious manner. diff --git a/vendor/github.com/go-co-op/gocron/executor.go b/vendor/github.com/go-co-op/gocron/executor.go index 43853fb21..76d2352ad 100644 --- a/vendor/github.com/go-co-op/gocron/executor.go +++ b/vendor/github.com/go-co-op/gocron/executor.go @@ -3,8 +3,9 @@ package gocron import ( "context" "sync" + "time" - "golang.org/x/sync/semaphore" + "go.uber.org/atomic" ) const ( @@ -21,91 +22,252 @@ const ( // 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.Every("1s").Do(func() { + // // this will result in an ever-growing number of goroutines + // // blocked trying to send to the buffered channel + // time.Sleep(10 * time.Minute) + // }) + WaitMode ) type executor struct { - jobFunctions chan jobFunction - stopCh chan struct{} - limitMode limitMode - maxRunningJobs *semaphore.Weighted + jobFunctions chan jobFunction // the chan upon which the jobFunctions are passed in from the scheduler + ctx context.Context // used to tell the executor to stop + cancel context.CancelFunc // used to tell the executor to stop + wg *sync.WaitGroup // used by the scheduler to wait for the executor to stop + jobsWg *sync.WaitGroup // used by the executor to wait for all jobs to finish + singletonWgs *sync.Map // used by the executor to wait for the singleton runners to complete + skipExecution *atomic.Bool // used to pause the execution of jobs + + limitMode limitMode // when SetMaxConcurrentJobs() is set upon the scheduler + limitModeMaxRunningJobs int // stores the maximum number of concurrently running jobs + limitModeFuncsRunning *atomic.Int64 // tracks the count of limited mode funcs running + limitModeFuncWg *sync.WaitGroup // allow the executor to wait for limit mode functions to wrap up + limitModeQueue chan jobFunction // pass job functions to the limit mode workers + limitModeQueueMu *sync.Mutex // for protecting the limitModeQueue + limitModeRunningJobs *atomic.Int64 // tracks the count of running jobs to check against the max + stopped *atomic.Bool // allow workers to drain the buffered limitModeQueue + + distributedLocker Locker // support running jobs across multiple instances } func newExecutor() executor { - return executor{ - jobFunctions: make(chan jobFunction, 1), - stopCh: make(chan struct{}, 1), + e := executor{ + jobFunctions: make(chan jobFunction, 1), + singletonWgs: &sync.Map{}, + limitModeFuncsRunning: atomic.NewInt64(0), + limitModeFuncWg: &sync.WaitGroup{}, + limitModeRunningJobs: atomic.NewInt64(0), + limitModeQueueMu: &sync.Mutex{}, + } + return e +} + +func runJob(f jobFunction) { + f.runStartCount.Add(1) + f.isRunning.Store(true) + callJobFunc(f.eventListeners.onBeforeJobExecution) + _ = callJobFuncWithParams(f.eventListeners.beforeJobRuns, []interface{}{f.getName()}) + err := callJobFuncWithParams(f.function, f.parameters) + if err != nil { + _ = callJobFuncWithParams(f.eventListeners.onError, []interface{}{f.getName(), err}) + } else { + _ = callJobFuncWithParams(f.eventListeners.noError, []interface{}{f.getName()}) + } + _ = callJobFuncWithParams(f.eventListeners.afterJobRuns, []interface{}{f.getName()}) + callJobFunc(f.eventListeners.onAfterJobExecution) + f.isRunning.Store(false) + f.runFinishCount.Add(1) +} + +func (jf *jobFunction) singletonRunner() { + jf.singletonRunnerOn.Store(true) + jf.singletonWgMu.Lock() + jf.singletonWg.Add(1) + jf.singletonWgMu.Unlock() + for { + select { + case <-jf.ctx.Done(): + jf.singletonWg.Done() + jf.singletonRunnerOn.Store(false) + jf.singletonQueueMu.Lock() + jf.singletonQueue = make(chan struct{}, 1000) + jf.singletonQueueMu.Unlock() + jf.stopped.Store(false) + return + case <-jf.singletonQueue: + if !jf.stopped.Load() { + runJob(*jf) + } + } + } +} + +func (e *executor) limitModeRunner() { + for { + select { + case <-e.ctx.Done(): + e.limitModeFuncsRunning.Inc() + e.limitModeFuncWg.Done() + return + case jf := <-e.limitModeQueue: + if !e.stopped.Load() { + e.runJob(jf) + } + } } } func (e *executor) start() { + e.wg = &sync.WaitGroup{} + e.wg.Add(1) + stopCtx, cancel := context.WithCancel(context.Background()) - runningJobsWg := sync.WaitGroup{} + e.ctx = stopCtx + e.cancel = cancel + + e.jobsWg = &sync.WaitGroup{} + + e.stopped = atomic.NewBool(false) + e.skipExecution = atomic.NewBool(false) + + e.limitModeQueueMu.Lock() + e.limitModeQueue = make(chan jobFunction, 1000) + e.limitModeQueueMu.Unlock() + go e.run() +} +func (e *executor) runJob(f jobFunction) { + switch f.runConfig.mode { + case defaultMode: + lockKey := f.jobName + if lockKey == "" { + lockKey = f.funcName + } + if e.distributedLocker != nil { + l, err := e.distributedLocker.Lock(f.ctx, lockKey) + if err != nil || l == nil { + return + } + defer func() { + durationToNextRun := time.Until(f.jobFuncNextRun) + if durationToNextRun > time.Second*5 { + durationToNextRun = time.Second * 5 + } + if durationToNextRun > time.Millisecond*100 { + timer := time.NewTimer(time.Duration(float64(durationToNextRun) * 0.9)) + defer timer.Stop() + + select { + case <-e.ctx.Done(): + case <-timer.C: + } + } + _ = l.Unlock(f.ctx) + }() + } + runJob(f) + case singletonMode: + e.singletonWgs.Store(f.singletonWg, f.singletonWgMu) + + if !f.singletonRunnerOn.Load() { + go f.singletonRunner() + } + f.singletonQueueMu.Lock() + f.singletonQueue <- struct{}{} + f.singletonQueueMu.Unlock() + } +} + +func (e *executor) run() { for { select { case f := <-e.jobFunctions: - runningJobsWg.Add(1) - go func() { - defer runningJobsWg.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 - } - } - } + if e.stopped.Load() || e.skipExecution.Load() { + continue + } + + if e.limitModeMaxRunningJobs > 0 { + countRunning := e.limitModeFuncsRunning.Load() + if countRunning < int64(e.limitModeMaxRunningJobs) { + diff := int64(e.limitModeMaxRunningJobs) - countRunning + for i := int64(0); i < diff; i++ { + e.limitModeFuncWg.Add(1) + go e.limitModeRunner() + e.limitModeFuncsRunning.Inc() } + } + } - defer e.maxRunningJobs.Release(1) + e.jobsWg.Add(1) + go func() { + defer e.jobsWg.Done() + + panicHandlerMutex.RLock() + defer panicHandlerMutex.RUnlock() + + if panicHandler != nil { + defer func() { + if r := recover(); r != nil { + panicHandler(f.funcName, r) + } + }() } - switch f.runConfig.mode { - case defaultMode: - f.incrementRunState() - callJobFuncWithParams(f.function, f.parameters) - f.decrementRunState() - case singletonMode: - _, _, _ = f.limiter.Do("main", func() (interface{}, error) { + if e.limitModeMaxRunningJobs > 0 { + switch e.limitMode { + case RescheduleMode: + if e.limitModeRunningJobs.Load() < int64(e.limitModeMaxRunningJobs) { + select { + case e.limitModeQueue <- f: + case <-e.ctx.Done(): + } + } + case WaitMode: select { - case <-stopCtx.Done(): - return nil, nil - case <-f.ctx.Done(): - return nil, nil - default: + case e.limitModeQueue <- f: + case <-e.ctx.Done(): } - f.incrementRunState() - callJobFuncWithParams(f.function, f.parameters) - f.decrementRunState() - return nil, nil - }) + } + return } + + e.runJob(f) }() - case <-e.stopCh: - cancel() - runningJobsWg.Wait() - e.stopCh <- struct{}{} + case <-e.ctx.Done(): + e.jobsWg.Wait() + e.wg.Done() return } } } func (e *executor) stop() { - e.stopCh <- struct{}{} - <-e.stopCh + e.stopped.Store(true) + e.cancel() + e.wg.Wait() + if e.singletonWgs != nil { + e.singletonWgs.Range(func(key, value interface{}) bool { + wg, wgOk := key.(*sync.WaitGroup) + mu, muOk := value.(*sync.Mutex) + if wgOk && muOk { + mu.Lock() + wg.Wait() + mu.Unlock() + } + return true + }) + } + if e.limitModeMaxRunningJobs > 0 { + e.limitModeFuncWg.Wait() + e.limitModeQueueMu.Lock() + e.limitModeQueue = nil + e.limitModeQueueMu.Unlock() + } } diff --git a/vendor/github.com/go-co-op/gocron/gocron.go b/vendor/github.com/go-co-op/gocron/gocron.go index cb840fe42..cb667022a 100644 --- a/vendor/github.com/go-co-op/gocron/gocron.go +++ b/vendor/github.com/go-co-op/gocron/gocron.go @@ -3,7 +3,6 @@ // An in-process scheduler for periodic jobs that uses the builder pattern // for configuration. gocron lets you run Golang functions periodically // at pre-determined intervals using a simple, human-friendly syntax. -// package gocron import ( @@ -12,27 +11,50 @@ import ( "reflect" "regexp" "runtime" + "sync" "time" ) +// PanicHandlerFunc represents a type that can be set to handle panics occurring +// during job execution. +type PanicHandlerFunc func(jobName string, recoverData interface{}) + +// The global panic handler +var ( + panicHandler PanicHandlerFunc + panicHandlerMutex = sync.RWMutex{} +) + +// SetPanicHandler sets the global panicHandler to the given function. +// Leaving it nil or setting it to nil disables automatic panic handling. +// If the panicHandler is not nil, any panic that occurs during executing a job will be recovered +// and the panicHandlerFunc will be called with the job's funcName and the recover data. +func SetPanicHandler(handler PanicHandlerFunc) { + panicHandlerMutex.Lock() + defer panicHandlerMutex.Unlock() + panicHandler = handler +} + // Error declarations for gocron related errors var ( - ErrNotAFunction = errors.New("only functions can be scheduled 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(".Every(time.Duration) and .Cron() 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") - ErrInvalidDayOfMonthEntry = errors.New("only days 1 through 28 are allowed for monthly schedules") - ErrTagsUnique = func(tag string) error { return fmt.Errorf("a non-unique tag was set on the job: %s", tag) } - ErrWrongParams = errors.New("wrong list of params") - ErrUpdateCalledWithoutJob = errors.New("a call to Scheduler.Update() requires a call to Scheduler.Job() first") - ErrCronParseFailure = errors.New("cron expression failed to be parsed") - ErrInvalidDaysOfMonthDuplicateValue = errors.New("duplicate days of month is not allowed in Month() and Months() methods") + ErrNotAFunction = errors.New("gocron: only functions can be scheduled into the job queue") + ErrNotScheduledWeekday = errors.New("gocron: job not scheduled weekly on a weekday") + ErrJobNotFoundWithTag = errors.New("gocron: no jobs found with given tag") + ErrUnsupportedTimeFormat = errors.New("gocron: the given time format is not supported") + ErrInvalidInterval = errors.New("gocron: .Every() interval must be greater than 0") + ErrInvalidIntervalType = errors.New("gocron: .Every() interval must be int, time.Duration, or string") + ErrInvalidIntervalUnitsSelection = errors.New("gocron: .Every(time.Duration) and .Cron() cannot be used with units (e.g. .Seconds())") + ErrInvalidFunctionParameters = errors.New("gocron: length of function parameters must match job function parameters") + + ErrAtTimeNotSupported = errors.New("gocron: the At() method is not supported for this time unit") + ErrWeekdayNotSupported = errors.New("gocron: weekday is not supported for time unit") + ErrInvalidDayOfMonthEntry = errors.New("gocron: only days 1 through 28 are allowed for monthly schedules") + ErrTagsUnique = func(tag string) error { return fmt.Errorf("gocron: a non-unique tag was set on the job: %s", tag) } + ErrWrongParams = errors.New("gocron: wrong list of params") + ErrDoWithJobDetails = errors.New("gocron: DoWithJobDetails expects a function whose last parameter is a gocron.Job") + ErrUpdateCalledWithoutJob = errors.New("gocron: a call to Scheduler.Update() requires a call to Scheduler.Job() first") + ErrCronParseFailure = errors.New("gocron: cron expression failed to be parsed") + ErrInvalidDaysOfMonthDuplicateValue = errors.New("gocron: duplicate days of month is not allowed in Month() and Months() methods") ) func wrapOrError(toWrap error, err error) error { @@ -66,22 +88,49 @@ const ( crontab ) -func callJobFuncWithParams(jobFunc interface{}, params []interface{}) { +func callJobFunc(jobFunc interface{}) { + if jobFunc == nil { + return + } + f := reflect.ValueOf(jobFunc) + if !f.IsZero() { + f.Call([]reflect.Value{}) + } +} + +func callJobFuncWithParams(jobFunc interface{}, params []interface{}) error { + if jobFunc == nil { + return nil + } f := reflect.ValueOf(jobFunc) + if f.IsZero() { + return nil + } if len(params) != f.Type().NumIn() { - return + return nil } in := make([]reflect.Value, len(params)) for k, param := range params { in[k] = reflect.ValueOf(param) } - f.Call(in) + vals := f.Call(in) + for _, val := range vals { + i := val.Interface() + if err, ok := i.(error); ok { + return err + } + } + return nil } func getFunctionName(fn interface{}) string { return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() } +func getFunctionNameOfPointer(fn interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(fn).Elem().Pointer()).Name() +} + 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 e52a0e10e..d72a0f118 100644 --- a/vendor/github.com/go-co-op/gocron/job.go +++ b/vendor/github.com/go-co-op/gocron/job.go @@ -3,57 +3,117 @@ package gocron import ( "context" "fmt" + "math/rand" "sort" "sync" - "sync/atomic" "time" "github.com/robfig/cron/v3" - "golang.org/x/sync/singleflight" + "go.uber.org/atomic" ) // Job struct stores the information necessary to run a Job type Job struct { - mu sync.RWMutex + mu *jobMutex jobFunction - interval int // pause interval * unit between runs + interval int // interval * unit between runs + random // details for randomness duration time.Duration // time duration between runs unit schedulingUnit // time units, e.g. 'minutes', 'hours'... startsImmediately bool // if the Job should run upon scheduler start atTimes []time.Duration // optional time(s) 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 - scheduledWeekdays []time.Weekday // Specific days of the week to start on - daysOfTheMonth []int // Specific days 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 // handles running tasks at specific time - cronSchedule cron.Schedule // stores the schedule when a task uses cron + + scheduledWeekdays []time.Weekday // Specific days of the week to start on + daysOfTheMonth []int // Specific days of the month to run the job + tags []string // allow the user to tag Jobs with certain labels + timer *time.Timer // handles running tasks at specific time + cronSchedule cron.Schedule // stores the schedule when a task uses cron + runWithDetails bool // when true the job is passed as the last arg of the jobFunc +} + +type jobRunTimes struct { + jobRunTimesMu *sync.Mutex + previousRun time.Time // datetime of the run before last run + lastRun time.Time // datetime of last run + nextRun time.Time // datetime of next run +} + +type random struct { + rand *rand.Rand + randomizeInterval bool // whether the interval is random + randomIntervalRange [2]int // random interval range } type jobFunction struct { - function interface{} // task's function - parameters []interface{} // task's function parameters - name string //nolint the function name to run - 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 - runState *int64 // will be non-zero when jobs are running -} - -func (jf *jobFunction) incrementRunState() { - if jf.runState != nil { - atomic.AddInt64(jf.runState, 1) + *jobRunTimes // tracking all the markers for job run times + eventListeners // additional functions to allow run 'em during job performing + function interface{} // task's function + parameters []interface{} // task's function parameters + parametersLen int // length of the passed parameters + jobName string // key of the distributed lock + funcName string // the name of the function - e.g. main.func1 + runConfig runConfig // configuration for how many times to run the job + singletonQueueMu *sync.Mutex // mutex for singletonQueue + singletonQueue chan struct{} // queues jobs for the singleton runner to handle + singletonRunnerOn *atomic.Bool // whether the runner function for singleton is running + ctx context.Context // for cancellation + cancel context.CancelFunc // for cancellation + isRunning *atomic.Bool // whether the job func is currently being run + runStartCount *atomic.Int64 // number of times the job was started + runFinishCount *atomic.Int64 // number of times the job was finished + singletonWg *sync.WaitGroup // used by singleton runner + singletonWgMu *sync.Mutex // use to protect the singletonWg + stopped *atomic.Bool // tracks whether the job is currently stopped + jobFuncNextRun time.Time // the next time the job is scheduled to run +} + +type eventListeners struct { + onAfterJobExecution interface{} // deprecated + onBeforeJobExecution interface{} // deprecated + beforeJobRuns func(jobName string) // called before the job executes + afterJobRuns func(jobName string) // called after the job executes + onError func(jobName string, err error) // called when the job returns an error + noError func(jobName string) // called when no error is returned +} + +type jobMutex struct { + sync.RWMutex +} + +func (jf *jobFunction) copy() jobFunction { + cp := jobFunction{ + jobRunTimes: jf.jobRunTimes, + eventListeners: jf.eventListeners, + function: jf.function, + parameters: nil, + parametersLen: jf.parametersLen, + funcName: jf.funcName, + jobName: jf.jobName, + runConfig: jf.runConfig, + singletonQueue: jf.singletonQueue, + singletonQueueMu: jf.singletonQueueMu, + ctx: jf.ctx, + cancel: jf.cancel, + isRunning: jf.isRunning, + runStartCount: jf.runStartCount, + runFinishCount: jf.runFinishCount, + singletonWg: jf.singletonWg, + singletonWgMu: jf.singletonWgMu, + singletonRunnerOn: jf.singletonRunnerOn, + stopped: jf.stopped, + jobFuncNextRun: jf.jobFuncNextRun, } + cp.parameters = append(cp.parameters, jf.parameters...) + return cp } -func (jf *jobFunction) decrementRunState() { - if jf.runState != nil { - atomic.AddInt64(jf.runState, -1) +func (jf *jobFunction) getName() string { + if jf.jobName != "" { + return jf.jobName } + return jf.funcName } type runConfig struct { @@ -76,16 +136,23 @@ const ( // newJob creates a new Job with the provided interval func newJob(interval int, startImmediately bool, singletonMode bool) *Job { ctx, cancel := context.WithCancel(context.Background()) - var zero int64 job := &Job{ + mu: &jobMutex{}, interval: interval, unit: seconds, - lastRun: time.Time{}, - nextRun: time.Time{}, jobFunction: jobFunction{ - ctx: ctx, - cancel: cancel, - runState: &zero, + jobRunTimes: &jobRunTimes{ + jobRunTimesMu: &sync.Mutex{}, + lastRun: time.Time{}, + nextRun: time.Time{}, + }, + ctx: ctx, + cancel: cancel, + isRunning: atomic.NewBool(false), + runStartCount: atomic.NewInt64(0), + runFinishCount: atomic.NewInt64(0), + singletonRunnerOn: atomic.NewBool(false), + stopped: atomic.NewBool(false), }, tags: []string{}, startsImmediately: startImmediately, @@ -96,8 +163,44 @@ func newJob(interval int, startImmediately bool, singletonMode bool) *Job { return job } +// Name sets the name of the current job. +// +// If the scheduler is running using WithDistributedLocker(), +// the job name is used as the distributed lock key. +func (j *Job) Name(name string) { + j.mu.Lock() + defer j.mu.Unlock() + j.jobName = name +} + +func (j *Job) setRandomInterval(a, b int) { + j.random.rand = rand.New(rand.NewSource(time.Now().UnixNano())) // nolint + + j.random.randomizeInterval = true + if a < b { + j.random.randomIntervalRange[0] = a + j.random.randomIntervalRange[1] = b + 1 + } else { + j.random.randomIntervalRange[0] = b + j.random.randomIntervalRange[1] = a + 1 + } +} + +func (j *Job) getRandomInterval() int { + randNum := j.rand.Intn(j.randomIntervalRange[1] - j.randomIntervalRange[0]) + return j.randomIntervalRange[0] + randNum +} + +func (j *Job) getInterval() int { + if j.randomizeInterval { + return j.getRandomInterval() + } + return j.interval +} + func (j *Job) neverRan() bool { - return j.lastRun.IsZero() + jobLastRun := j.LastRun() + return jobLastRun.IsZero() } func (j *Job) getStartsImmediately() bool { @@ -174,10 +277,14 @@ func (j *Job) addAtTime(t time.Duration) { } func (j *Job) getStartAtTime() time.Time { + j.mu.RLock() + defer j.mu.RUnlock() return j.startAtTime } func (j *Job) setStartAtTime(t time.Time) { + j.mu.Lock() + defer j.mu.Unlock() j.startAtTime = t } @@ -205,6 +312,12 @@ func (j *Job) setDuration(t time.Duration) { j.duration = t } +func (j *Job) setInterval(i int) { + j.mu.Lock() + defer j.mu.Unlock() + j.interval = i +} + // hasTags returns true if all tags are matched on this Job func (j *Job) hasTags(tags ...string) bool { // Build map of all Job tags for easy comparison @@ -230,6 +343,11 @@ func (j *Job) Error() error { return j.error } +// Context returns the job's context. The context controls cancellation. +func (j *Job) Context() context.Context { + return j.ctx +} + // Tag allows you to add arbitrary labels to a Job that do not // impact the functionality of the Job func (j *Job) Tag(tags ...string) { @@ -253,6 +371,64 @@ func (j *Job) Tags() []string { return j.tags } +// EventListener functions utilize the job's name and are triggered +// by or in the condition that the name suggests +type EventListener func(j *Job) + +// BeforeJobRuns is called before the job is run +func BeforeJobRuns(eventListenerFunc func(jobName string)) EventListener { + return func(j *Job) { + j.mu.Lock() + defer j.mu.Unlock() + j.eventListeners.beforeJobRuns = eventListenerFunc + } +} + +// AfterJobRuns is called after the job is run +// This is called even when an error is returned +func AfterJobRuns(eventListenerFunc func(jobName string)) EventListener { + return func(j *Job) { + j.mu.Lock() + defer j.mu.Unlock() + j.eventListeners.afterJobRuns = eventListenerFunc + } +} + +// WhenJobReturnsError is called when the job returns an error +func WhenJobReturnsError(eventListenerFunc func(jobName string, err error)) EventListener { + return func(j *Job) { + j.mu.Lock() + defer j.mu.Unlock() + j.eventListeners.onError = eventListenerFunc + } +} + +// WhenJobReturnsNoError is called when the job does not return an error +// the function must accept a single parameter, which is an error +func WhenJobReturnsNoError(eventListenerFunc func(jobName string)) EventListener { + return func(j *Job) { + j.mu.Lock() + defer j.mu.Unlock() + j.eventListeners.noError = eventListenerFunc + } +} + +// RegisterEventListeners accepts EventListeners and registers them for the job +// The event listeners are then called at the times described by each listener. +func (j *Job) RegisterEventListeners(eventListeners ...EventListener) { + for _, el := range eventListeners { + el(j) + } +} + +// Deprecated: SetEventListeners accepts two functions that will be called, one before and one after the job is run +func (j *Job) SetEventListeners(onBeforeJobExecution interface{}, onAfterJobExecution interface{}) { + j.eventListeners = eventListeners{ + onBeforeJobExecution: onBeforeJobExecution, + onAfterJobExecution: onAfterJobExecution, + } +} + // ScheduledTime returns the time of the Job's next scheduled run func (j *Job) ScheduledTime() time.Time { j.mu.RLock() @@ -264,17 +440,17 @@ func (j *Job) ScheduledTime() time.Time { // If multiple times are set, the earliest time will be returned. func (j *Job) ScheduledAtTime() string { if len(j.atTimes) == 0 { - return "0:0" + return "00:00" } - return fmt.Sprintf("%d:%d", j.getFirstAtTime()/time.Hour, (j.getFirstAtTime()%time.Hour)/time.Minute) + return fmt.Sprintf("%02d:%02d", j.getFirstAtTime()/time.Hour, (j.getFirstAtTime()%time.Hour)/time.Minute) } // ScheduledAtTimes returns the specific times of day the Job will run at func (j *Job) ScheduledAtTimes() []string { r := make([]string, len(j.atTimes)) for i, t := range j.atTimes { - r[i] = fmt.Sprintf("%d:%d", t/time.Hour, (t%time.Hour)/time.Minute) + r[i] = fmt.Sprintf("%02d:%02d", t/time.Hour, (t%time.Hour)/time.Minute) } return r @@ -325,8 +501,16 @@ func (j *Job) SingletonMode() { j.mu.Lock() defer j.mu.Unlock() j.runConfig.mode = singletonMode - j.jobFunction.limiter = &singleflight.Group{} + j.jobFunction.singletonWgMu = &sync.Mutex{} + j.jobFunction.singletonWgMu.Lock() + j.jobFunction.singletonWg = &sync.WaitGroup{} + j.jobFunction.singletonWgMu.Unlock() + + j.jobFunction.singletonQueueMu = &sync.Mutex{} + j.jobFunction.singletonQueueMu.Lock() + j.jobFunction.singletonQueue = make(chan struct{}, 100) + j.jobFunction.singletonQueueMu.Unlock() } // shouldRun evaluates if this job should run again @@ -334,34 +518,54 @@ func (j *Job) SingletonMode() { func (j *Job) shouldRun() bool { j.mu.RLock() defer j.mu.RUnlock() - return !j.runConfig.finiteRuns || j.runCount < j.runConfig.maxRuns + return !j.runConfig.finiteRuns || j.runStartCount.Load() < int64(j.runConfig.maxRuns) } // LastRun returns the time the job was run last func (j *Job) LastRun() time.Time { + j.jobRunTimesMu.Lock() + defer j.jobRunTimesMu.Unlock() return j.lastRun } func (j *Job) setLastRun(t time.Time) { + j.previousRun = j.lastRun j.lastRun = t } // NextRun returns the time the job will run next func (j *Job) NextRun() time.Time { - j.mu.RLock() - defer j.mu.RUnlock() + j.jobRunTimesMu.Lock() + defer j.jobRunTimesMu.Unlock() return j.nextRun } func (j *Job) setNextRun(t time.Time) { - j.mu.Lock() - defer j.mu.Unlock() + j.jobRunTimesMu.Lock() + defer j.jobRunTimesMu.Unlock() j.nextRun = t + j.jobFunction.jobFuncNextRun = t +} + +// PreviousRun returns the job run time previous to LastRun +func (j *Job) PreviousRun() time.Time { + j.jobRunTimesMu.Lock() + defer j.jobRunTimesMu.Unlock() + return j.previousRun } -// RunCount returns the number of time the job ran so far +// RunCount returns the number of times the job has been started func (j *Job) RunCount() int { - return j.runCount + j.mu.Lock() + defer j.mu.Unlock() + return int(j.runStartCount.Load()) +} + +// FinishedRunCount returns the number of times the job has finished running +func (j *Job) FinishedRunCount() int { + j.mu.Lock() + defer j.mu.Unlock() + return int(j.runFinishCount.Load()) } func (j *Job) stop() { @@ -372,10 +576,33 @@ func (j *Job) stop() { } if j.cancel != nil { j.cancel() + j.ctx, j.cancel = context.WithCancel(context.Background()) } + j.stopped.Store(true) } // IsRunning reports whether any instances of the job function are currently running func (j *Job) IsRunning() bool { - return atomic.LoadInt64(j.runState) != 0 + return j.isRunning.Load() +} + +// you must Lock the job before calling copy +func (j *Job) copy() Job { + return Job{ + mu: &jobMutex{}, + jobFunction: j.jobFunction, + interval: j.interval, + duration: j.duration, + unit: j.unit, + startsImmediately: j.startsImmediately, + atTimes: j.atTimes, + startAtTime: j.startAtTime, + error: j.error, + scheduledWeekdays: j.scheduledWeekdays, + daysOfTheMonth: j.daysOfTheMonth, + tags: j.tags, + timer: j.timer, + cronSchedule: j.cronSchedule, + runWithDetails: j.runWithDetails, + } } diff --git a/vendor/github.com/go-co-op/gocron/locker.go b/vendor/github.com/go-co-op/gocron/locker.go new file mode 100644 index 000000000..dc713f9b3 --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/locker.go @@ -0,0 +1,23 @@ +package gocron + +import ( + "context" + "errors" +) + +var ( + ErrFailedToConnectToRedis = errors.New("gocron: failed to connect to redis") + ErrFailedToObtainLock = errors.New("gocron: failed to obtain lock") + ErrFailedToReleaseLock = errors.New("gocron: failed to release lock") +) + +// Locker represents the required interface to lock jobs when running multiple schedulers. +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 +type Lock interface { + Unlock(ctx context.Context) error +} diff --git a/vendor/github.com/go-co-op/gocron/scheduler.go b/vendor/github.com/go-co-op/gocron/scheduler.go index b92da946a..7b763d24c 100644 --- a/vendor/github.com/go-co-op/gocron/scheduler.go +++ b/vendor/github.com/go-co-op/gocron/scheduler.go @@ -10,24 +10,24 @@ import ( "time" "github.com/robfig/cron/v3" - "golang.org/x/sync/semaphore" + "go.uber.org/atomic" ) type limitMode int8 // Scheduler struct stores a list of Jobs and the location of time used by the Scheduler, -// and implements the sort.Interface{} for sorting Jobs, by the time of nextRun +// and implements the sort. any for sorting Jobs, by the time of jobFuncNextRun type Scheduler struct { jobsMutex sync.RWMutex jobs []*Job locationMutex sync.RWMutex location *time.Location - runningMutex sync.RWMutex - running bool // represents if the scheduler is running at the moment or not + running *atomic.Bool // represents if the scheduler is running at the moment or not - time timeWrapper // wrapper around time.Time - executor *executor // executes jobs passed via chan + time TimeWrapper // wrapper around time.Time + timer func(d time.Duration, f func()) *time.Timer + executor *executor // executes jobs passed via chan tags sync.Map // for storing tags when unique tags is set @@ -35,7 +35,17 @@ type Scheduler struct { updateJob bool // so the scheduler knows to create a new job or update the current waitForInterval bool // defaults jobs to waiting for first interval to start singletonMode bool // defaults all jobs to use SingletonMode() - jobCreated bool // so the scheduler knows a job was created prior to calling Every or Cron + + startBlockingStopChanMutex sync.Mutex + startBlockingStopChan chan struct{} // stops the scheduler + + // tracks whether we're in a chain of scheduling methods for a job + // a chain is started with any of the scheduler methods that operate + // upon a job and are ended with one of [ Do(), Update() ] - note that + // Update() calls Do(), so really they all end with Do(). + // This allows the caller to begin with any job related scheduler method + // and only with one of [ Every(), EveryRandom(), Cron(), CronWithSeconds(), MonthFirstWeekday() ] + inScheduleChain bool } // days in a week @@ -48,24 +58,36 @@ func NewScheduler(loc *time.Location) *Scheduler { return &Scheduler{ jobs: make([]*Job, 0), location: loc, - running: false, + running: atomic.NewBool(false), time: &trueTime{}, executor: &executor, tagsUnique: false, + timer: afterFunc, } } // 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. +// +// Note: WaitMode and RescheduleMode provide details on usage and potential risks. func (s *Scheduler) SetMaxConcurrentJobs(n int, mode limitMode) { - s.executor.maxRunningJobs = semaphore.NewWeighted(int64(n)) + s.executor.limitModeMaxRunningJobs = n s.executor.limitMode = mode } -// StartBlocking starts all jobs and blocks the current thread +// StartBlocking starts all jobs and blocks the current thread. +// This blocking method can be stopped with Stop() from a separate goroutine. func (s *Scheduler) StartBlocking() { s.StartAsync() - <-make(chan bool) + s.startBlockingStopChanMutex.Lock() + s.startBlockingStopChan = make(chan struct{}, 1) + s.startBlockingStopChanMutex.Unlock() + + <-s.startBlockingStopChan + + s.startBlockingStopChanMutex.Lock() + s.startBlockingStopChan = nil + s.startBlockingStopChanMutex.Unlock() } // StartAsync starts all jobs without blocking the current thread @@ -75,30 +97,31 @@ func (s *Scheduler) StartAsync() { } } -//start starts the scheduler, scheduling and running jobs +// start starts the scheduler, scheduling and running jobs func (s *Scheduler) start() { - go s.executor.start() + s.executor.start() s.setRunning(true) s.runJobs(s.Jobs()) } func (s *Scheduler) runJobs(jobs []*Job) { for _, job := range jobs { - s.scheduleNextRun(job) + ctx, cancel := context.WithCancel(context.Background()) + job.mu.Lock() + job.ctx = ctx + job.cancel = cancel + job.mu.Unlock() + s.runContinuous(job) } } func (s *Scheduler) setRunning(b bool) { - s.runningMutex.Lock() - defer s.runningMutex.Unlock() - s.running = b + s.running.Store(b) } // IsRunning returns true if the scheduler is running func (s *Scheduler) IsRunning() bool { - s.runningMutex.RLock() - defer s.runningMutex.RUnlock() - return s.running + return s.running.Load() } // Jobs returns the list of Jobs from the Scheduler @@ -108,6 +131,16 @@ func (s *Scheduler) Jobs() []*Job { return s.jobs } +// Name sets the name of the current job. +// +// If the scheduler is running using WithDistributedLocker(), the job name is used +// as the distributed lock key. If the job name is not set, the function name is used as the distributed lock key. +func (s *Scheduler) Name(name string) *Scheduler { + job := s.getCurrentJob() + job.jobName = name + return s +} + func (s *Scheduler) setJobs(jobs []*Job) { s.jobsMutex.Lock() defer s.jobsMutex.Unlock() @@ -155,67 +188,72 @@ type nextRun struct { } // scheduleNextRun Compute the instant when this Job should run next -func (s *Scheduler) scheduleNextRun(job *Job) { +func (s *Scheduler) scheduleNextRun(job *Job) (bool, nextRun) { now := s.now() - lastRun := job.LastRun() if !s.jobPresent(job) { - return + return false, nextRun{} } - if job.getStartsImmediately() { - s.run(job) - lastRun = now - job.setStartsImmediately(false) - } + lastRun := now if job.neverRan() { // Increment startAtTime to the future if !job.startAtTime.IsZero() && job.startAtTime.Before(now) { - duration := s.durationToNextRun(job.startAtTime, job).duration - job.startAtTime = job.startAtTime.Add(duration) + dur := s.durationToNextRun(job.startAtTime, job).duration + job.setStartAtTime(job.startAtTime.Add(dur)) if job.startAtTime.Before(now) { diff := now.Sub(job.startAtTime) - duration := s.durationToNextRun(job.startAtTime, job).duration - count := diff / duration - if diff%duration != 0 { - count++ + dur := s.durationToNextRun(job.startAtTime, job).duration + var count time.Duration + if dur != 0 { + count = diff / dur + if diff%dur != 0 { + count++ + } } - job.startAtTime = job.startAtTime.Add(duration * count) + job.setStartAtTime(job.startAtTime.Add(dur * count)) } } - lastRun = now + } else { + lastRun = job.NextRun() } if !job.shouldRun() { s.RemoveByReference(job) - return + return false, nextRun{} } next := s.durationToNextRun(lastRun, job) + jobNextRun := job.NextRun() + if jobNextRun.After(now) { + job.setLastRun(now) + } else { + job.setLastRun(jobNextRun) + } + if next.dateTime.IsZero() { - job.setNextRun(lastRun.Add(next.duration)) + next.dateTime = lastRun.Add(next.duration) + job.setNextRun(next.dateTime) } else { job.setNextRun(next.dateTime) } - job.setTimer(time.AfterFunc(next.duration, func() { - if !next.dateTime.IsZero() { - for { - if time.Now().Unix() >= next.dateTime.Unix() { - break - } - } - } - s.run(job) - s.scheduleNextRun(job) - })) + return true, next } // durationToNextRun calculate how much time to the next run, depending on unit func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) nextRun { // job can be scheduled with .StartAt() - if job.getStartAtTime().After(lastRun) { - return nextRun{duration: job.getStartAtTime().Sub(s.now()), dateTime: job.getStartAtTime()} + if job.getFirstAtTime() == 0 && job.getStartAtTime().After(lastRun) { + sa := job.getStartAtTime() + if job.unit == days || job.unit == weeks || job.unit == months { + job.addAtTime( + time.Duration(sa.Hour())*time.Hour + + time.Duration(sa.Minute())*time.Minute + + time.Duration(sa.Second())*time.Second, + ) + } + return nextRun{duration: sa.Sub(s.now()), dateTime: sa} } var next nextRun @@ -230,6 +268,9 @@ func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) nextRun { } else { next = s.calculateWeeks(job, lastRun) } + if next.dateTime.Before(job.getStartAtTime()) { + return s.durationToNextRun(job.getStartAtTime(), job) + } case months: next = s.calculateMonths(job, lastRun) case duration: @@ -242,15 +283,12 @@ func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) nextRun { } func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) nextRun { - lastRunRoundedMidnight := s.roundToMidnight(lastRun) - // Special case: the last day of the month if len(job.daysOfTheMonth) == 1 && job.daysOfTheMonth[0] == -1 { return calculateNextRunForLastDayOfMonth(s, job, lastRun) } if len(job.daysOfTheMonth) != 0 { // calculate days to job.daysOfTheMonth - nextRunDateMap := make(map[int]nextRun) for _, day := range job.daysOfTheMonth { nextRunDateMap[day] = calculateNextRunForMonth(s, job, lastRun, day) @@ -267,7 +305,7 @@ func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) nextRun { return nextRunResult } - next := lastRunRoundedMidnight.Add(job.getFirstAtTime()).AddDate(0, job.interval, 0) + next := s.roundToMidnightAndAddDSTAware(lastRun, job.getFirstAtTime()).AddDate(0, job.getInterval(), 0) return nextRun{duration: until(lastRun, next), dateTime: next} } @@ -275,10 +313,10 @@ func calculateNextRunForLastDayOfMonth(s *Scheduler, job *Job, lastRun time.Time // Calculate the last day of the next month, by adding job.interval+1 months (i.e. the // first day of the month after the next month), and subtracting one day, unless the // last run occurred before the end of the month. - addMonth := job.interval + addMonth := job.getInterval() atTime := job.getAtTime(lastRun) if testDate := lastRun.AddDate(0, 0, 1); testDate.Month() != lastRun.Month() && - !s.roundToMidnight(lastRun).Add(atTime).After(lastRun) { + !s.roundToMidnightAndAddDSTAware(lastRun, atTime).After(lastRun) { // Our last run was on the last day of this month. addMonth++ atTime = job.getFirstAtTime() @@ -294,18 +332,21 @@ func calculateNextRunForLastDayOfMonth(s *Scheduler, job *Job, lastRun time.Time func calculateNextRunForMonth(s *Scheduler, job *Job, lastRun time.Time, dayOfMonth int) nextRun { atTime := job.getAtTime(lastRun) natTime := atTime - jobDay := time.Date(lastRun.Year(), lastRun.Month(), dayOfMonth, 0, 0, 0, 0, s.Location()).Add(atTime) + + hours, minutes, seconds := s.deconstructDuration(atTime) + jobDay := time.Date(lastRun.Year(), lastRun.Month(), dayOfMonth, hours, minutes, seconds, 0, s.Location()) + difference := absDuration(lastRun.Sub(jobDay)) next := lastRun if jobDay.Before(lastRun) { // shouldn't run this month; schedule for next interval minus day difference - next = next.AddDate(0, job.interval, -0) + next = next.AddDate(0, job.getInterval(), -0) next = next.Add(-difference) natTime = job.getFirstAtTime() } else { - if job.interval == 1 && !jobDay.Equal(lastRun) { // every month counts current month - next = next.AddDate(0, job.interval-1, 0) + if job.getInterval() == 1 && !jobDay.Equal(lastRun) { // every month counts current month + next = next.AddDate(0, job.getInterval()-1, 0) } else { // should run next month interval - next = next.AddDate(0, job.interval, 0) + next = next.AddDate(0, job.getInterval(), 0) natTime = job.getFirstAtTime() } next = next.Add(difference) @@ -323,26 +364,41 @@ func (s *Scheduler) calculateWeekday(job *Job, lastRun time.Time) nextRun { if totalDaysDifference > 0 { acTime = job.getFirstAtTime() } - next := s.roundToMidnight(lastRun).Add(acTime).AddDate(0, 0, totalDaysDifference) + next := s.roundToMidnightAndAddDSTAware(lastRun, acTime).AddDate(0, 0, totalDaysDifference) return nextRun{duration: until(lastRun, next), dateTime: next} } func (s *Scheduler) calculateWeeks(job *Job, lastRun time.Time) nextRun { - totalDaysDifference := int(job.interval) * 7 - next := s.roundToMidnight(lastRun).Add(job.getFirstAtTime()).AddDate(0, 0, totalDaysDifference) + totalDaysDifference := int(job.getInterval()) * 7 + + var next time.Time + + atTimes := job.atTimes + for _, at := range atTimes { + n := s.roundToMidnightAndAddDSTAware(lastRun, at) + if n.After(s.now()) { + next = n + break + } + } + + if next.IsZero() { + next = s.roundToMidnightAndAddDSTAware(lastRun, job.getFirstAtTime()).AddDate(0, 0, totalDaysDifference) + } + return nextRun{duration: until(lastRun, next), dateTime: next} } func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekday int, job *Job) int { - if job.interval > 1 && job.RunCount() < len(job.Weekdays()) { // just count weeks after the first jobs were done - return daysToWeekday - } - if job.interval > 1 && job.RunCount() >= len(job.Weekdays()) { + if job.getInterval() > 1 { + // just count weeks after the first jobs were done + if job.RunCount() < len(job.Weekdays()) { + return daysToWeekday + } if daysToWeekday > 0 { - return int(job.interval)*7 - (allWeekDays - daysToWeekday) + return int(job.getInterval())*7 - (allWeekDays - daysToWeekday) } - - return int(job.interval) * 7 + return int(job.getInterval()) * 7 } if daysToWeekday == 0 { // today, at future time or already passed @@ -356,22 +412,15 @@ func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekda } func (s *Scheduler) calculateDays(job *Job, lastRun time.Time) nextRun { - - if job.interval == 1 { - lastRunDayPlusJobAtTime := s.roundToMidnight(lastRun).Add(job.getAtTime(lastRun)) - - // handle occasional occurrence of job running to quickly / too early such that last run was within a second of now - lastRunUnix, nowUnix := job.LastRun().Unix(), s.now().Unix() - if lastRunUnix == nowUnix || lastRunUnix == nowUnix-1 || lastRunUnix == nowUnix+1 { - lastRun = lastRunDayPlusJobAtTime - } + if job.getInterval() == 1 { + lastRunDayPlusJobAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)) if shouldRunToday(lastRun, lastRunDayPlusJobAtTime) { return nextRun{duration: until(lastRun, lastRunDayPlusJobAtTime), dateTime: lastRunDayPlusJobAtTime} } } - nextRunAtTime := s.roundToMidnight(lastRun).Add(job.getFirstAtTime()).AddDate(0, 0, job.interval).In(s.Location()) + nextRunAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getFirstAtTime()).AddDate(0, 0, job.getInterval()).In(s.Location()) return nextRun{duration: until(lastRun, nextRunAtTime), dateTime: nextRunAtTime} } @@ -396,15 +445,7 @@ func in(scheduleWeekdays []time.Weekday, weekday time.Weekday) bool { } func (s *Scheduler) calculateDuration(job *Job) time.Duration { - 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 - now := s.time.Now(s.location) - next := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, s.Location()).Add(job.getFirstAtTime()) - if now.Before(next) || now.Equal(next) { - return next.Sub(now) - } - } - - interval := job.interval + interval := job.getInterval() switch job.getUnit() { case milliseconds: return time.Duration(interval) * time.Millisecond @@ -417,10 +458,6 @@ func (s *Scheduler) calculateDuration(job *Job) time.Duration { } } -func shouldRunAtSpecificTime(job *Job) bool { - return job.getAtTime(job.lastRun) != 0 -} - func (s *Scheduler) remainingDaysToWeekday(lastRun time.Time, job *Job) int { weekDays := job.Weekdays() sort.Slice(weekDays, func(i, j int) bool { @@ -438,7 +475,7 @@ func (s *Scheduler) remainingDaysToWeekday(lastRun time.Time, job *Job) int { }) // check atTime if equals { - if s.roundToMidnight(lastRun).Add(job.getAtTime(lastRun)).After(lastRun) { + if s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)).After(lastRun) { return 0 } index++ @@ -459,9 +496,17 @@ func absDuration(a time.Duration) time.Duration { return -a } -// 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.Location()) +func (s *Scheduler) deconstructDuration(d time.Duration) (hours int, minutes int, seconds int) { + hours = int(d.Seconds()) / int(time.Hour/time.Second) + minutes = (int(d.Seconds()) % int(time.Hour/time.Second)) / int(time.Minute/time.Second) + seconds = int(d.Seconds()) % int(time.Minute/time.Second) + return +} + +// roundToMidnightAndAddDSTAware truncates time to midnight and "adds" duration in a DST aware manner +func (s *Scheduler) roundToMidnightAndAddDSTAware(t time.Time, d time.Duration) time.Time { + hours, minutes, seconds := s.deconstructDuration(d) + return time.Date(t.Year(), t.Month(), t.Day(), hours, minutes, seconds, 0, s.Location()) } // NextRun datetime when the next Job should run. @@ -475,40 +520,37 @@ func (s *Scheduler) NextRun() (*Job, time.Time) { return s.Jobs()[0], s.Jobs()[0].NextRun() } +// EveryRandom schedules a new period Job that runs at random intervals +// between the provided lower (inclusive) and upper (inclusive) bounds. +// The default unit is Seconds(). Call a different unit in the chain +// if you would like to change that. For example, Minutes(), Hours(), etc. +func (s *Scheduler) EveryRandom(lower, upper int) *Scheduler { + job := s.getCurrentJob() + + job.setRandomInterval(lower, upper) + return s +} + // 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". +// +// The job is run immediately, unless StartAt or At is set. func (s *Scheduler) Every(interval interface{}) *Scheduler { - job := &Job{} - if s.updateJob || s.jobCreated { - job = s.getCurrentJob() - } + job := s.getCurrentJob() switch interval := interval.(type) { case int: - if !(s.updateJob || s.jobCreated) { - job = s.newJob(interval) - } else { - job = s.newJob(interval) - } + job.interval = interval if interval <= 0 { job.error = wrapOrError(job.error, ErrInvalidInterval) } case time.Duration: - if !(s.updateJob || s.jobCreated) { - job = s.newJob(0) - } else { - job.interval = 0 - } + job.setInterval(0) job.setDuration(interval) job.setUnit(duration) case string: - if !(s.updateJob || s.jobCreated) { - job = s.newJob(0) - } else { - job.interval = 0 - } d, err := time.ParseDuration(interval) if err != nil { job.error = wrapOrError(job.error, err) @@ -516,23 +558,9 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler { job.setDuration(d) job.setUnit(duration) default: - if !(s.updateJob || s.jobCreated) { - job = s.newJob(0) - } else { - job.interval = 0 - } job.error = wrapOrError(job.error, ErrInvalidIntervalType) } - if s.updateJob || s.jobCreated { - s.setJobs(append(s.Jobs()[:len(s.Jobs())-1], job)) - if s.jobCreated { - s.jobCreated = false - } - } else { - s.setJobs(append(s.Jobs(), job)) - } - return s } @@ -542,10 +570,67 @@ func (s *Scheduler) run(job *Job) { } job.mu.Lock() + + if job.function == nil { + job.mu.Unlock() + s.Remove(job) + return + } + defer job.mu.Unlock() - job.setLastRun(s.now()) - job.runCount++ - s.executor.jobFunctions <- job.jobFunction + + if job.runWithDetails { + switch len(job.parameters) { + case job.parametersLen: + job.parameters = append(job.parameters, job.copy()) + case job.parametersLen + 1: + job.parameters[job.parametersLen] = job.copy() + default: + // something is really wrong and we should never get here + job.error = wrapOrError(job.error, ErrInvalidFunctionParameters) + return + } + } + + s.executor.jobFunctions <- job.jobFunction.copy() +} + +func (s *Scheduler) runContinuous(job *Job) { + shouldRun, next := s.scheduleNextRun(job) + if !shouldRun { + return + } + + if !job.getStartsImmediately() { + job.setStartsImmediately(true) + } else { + s.run(job) + } + nr := next.dateTime.Sub(s.now()) + if nr < 0 { + job.setLastRun(s.now()) + shouldRun, next := s.scheduleNextRun(job) + if !shouldRun { + return + } + nr = next.dateTime.Sub(s.now()) + } + + job.setTimer(s.timer(nr, func() { + if !next.dateTime.IsZero() { + for { + n := s.now().UnixNano() - next.dateTime.UnixNano() + if n >= 0 { + break + } + select { + case <-s.executor.ctx.Done(): + case <-time.After(time.Duration(n)): + } + } + } + s.runContinuous(job) + })) } // RunAll run all Jobs regardless if they are scheduled to run or not @@ -584,16 +669,20 @@ func (s *Scheduler) RunByTagWithDelay(tag string, d time.Duration) error { // Remove specific 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(). +// been started by the job's timer before being removed, the only way to stop +// it through gocron is to use DoWithJobDetails and access the job's Context which +// informs you when the job has been canceled. +// +// Alternatively, the job function would need to have implemented a means of +// stopping, e.g. using a context.WithCancel() passed as params to Do method. +// +// The above are based on what the underlying library suggests https://pkg.go.dev/time#Timer.Stop. func (s *Scheduler) Remove(job interface{}) { fName := getFunctionName(job) j := s.findJobByTaskName(fName) s.removeJobsUniqueTags(j) s.removeByCondition(func(someJob *Job) bool { - return someJob.name == fName + return someJob.funcName == fName }) } @@ -609,7 +698,7 @@ func (s *Scheduler) RemoveByReference(job *Job) { func (s *Scheduler) findJobByTaskName(name string) *Job { for _, job := range s.Jobs() { - if job.name == name { + if job.funcName == name { return job } } @@ -696,6 +785,17 @@ Jobs: return nil, ErrJobNotFoundWithTag } +// MonthFirstWeekday sets the job to run the first specified weekday of the month +func (s *Scheduler) MonthFirstWeekday(weekday time.Weekday) *Scheduler { + _, month, day := s.time.Now(time.UTC).Date() + + if day < 7 { + return s.Cron(fmt.Sprintf("0 0 %d %d %d", day, month, weekday)) + } + + return s.Cron(fmt.Sprintf("0 0 %d %d %d", day, month+1, weekday)) +} + // 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 { @@ -706,6 +806,16 @@ func (s *Scheduler) LimitRunsTo(i int) *Scheduler { // SingletonMode prevents a new job from starting if the prior job has not yet // completed its run +// +// 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.Every("1s").SingletonMode().Do(func() { +// // this will result in an ever-growing number of goroutines +// // blocked trying to send to the buffered channel +// time.Sleep(10 * time.Minute) +// }) func (s *Scheduler) SingletonMode() *Scheduler { job := s.getCurrentJob() job.SingletonMode() @@ -714,6 +824,19 @@ func (s *Scheduler) SingletonMode() *Scheduler { // SingletonModeAll prevents new jobs from starting if the prior instance of the // particular job has not yet completed its run +// +// 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(time.UTC) +// s.SingletonModeAll() +// +// s.Every("1s").Do(func() { +// // this will result in an ever-growing number of goroutines +// // blocked trying to send to the buffered channel +// time.Sleep(10 * time.Minute) +// }) func (s *Scheduler) SingletonModeAll() { s.singletonMode = true } @@ -721,7 +844,7 @@ func (s *Scheduler) SingletonModeAll() { // TaskPresent checks if specific job's function was added to the scheduler. func (s *Scheduler) TaskPresent(j interface{}) bool { for _, job := range s.Jobs() { - if job.name == getFunctionName(j) { + if job.funcName == getFunctionName(j) { return true } } @@ -769,16 +892,25 @@ func (s *Scheduler) Stop() { } func (s *Scheduler) stop() { - s.setRunning(false) + s.stopJobs(s.jobs) s.executor.stop() + s.StopBlockingChan() + s.setRunning(false) } -// Do specifies the jobFunc that should be called every time the Job runs -func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) { +func (s *Scheduler) stopJobs(jobs []*Job) { + for _, job := range jobs { + job.stop() + } +} + +func (s *Scheduler) doCommon(jobFun interface{}, params ...interface{}) (*Job, error) { job := s.getCurrentJob() + s.inScheduleChain = false jobUnit := job.getUnit() - if job.getAtTime(job.lastRun) != 0 && (jobUnit <= hours || jobUnit >= duration) { + jobLastRun := job.LastRun() + if job.getAtTime(jobLastRun) != 0 && (jobUnit <= hours || jobUnit >= duration) { job.error = wrapOrError(job.error, ErrAtTimeNotSupported) } @@ -786,7 +918,7 @@ func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) job.error = wrapOrError(job.error, ErrWeekdayNotSupported) } - if job.unit != crontab && job.interval == 0 { + if job.unit != crontab && job.getInterval() == 0 { if job.unit != duration { job.error = wrapOrError(job.error, ErrInvalidInterval) } @@ -799,35 +931,75 @@ func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) return nil, job.error } - typ := reflect.TypeOf(jobFun) - if typ.Kind() != reflect.Func { + val := reflect.ValueOf(jobFun) + for val.Kind() == reflect.Ptr { + val = val.Elem() + } + + if val.Kind() != reflect.Func { // delete the job for the same reason as above s.RemoveByReference(job) return nil, ErrNotAFunction } - f := reflect.ValueOf(jobFun) - if len(params) != f.Type().NumIn() { + var fname string + if val == reflect.ValueOf(jobFun) { + fname = getFunctionName(jobFun) + } else { + fname = getFunctionNameOfPointer(jobFun) + } + + if job.funcName != fname { + job.function = jobFun + if val != reflect.ValueOf(jobFun) { + job.function = val.Interface() + } + + job.parameters = params + job.funcName = fname + } + + expectedParamLength := val.Type().NumIn() + if job.runWithDetails { + expectedParamLength-- + } + + if len(params) != expectedParamLength { s.RemoveByReference(job) job.error = wrapOrError(job.error, ErrWrongParams) return nil, job.error } - fname := getFunctionName(jobFun) - if job.name != fname { - job.function = jobFun - job.parameters = params - job.name = fname + if job.runWithDetails && val.Type().In(len(params)).Kind() != reflect.ValueOf(*job).Kind() { + s.RemoveByReference(job) + job.error = wrapOrError(job.error, ErrDoWithJobDetails) + return nil, job.error } // we should not schedule if not running since we can't foresee how long it will take for the scheduler to start if s.IsRunning() { - s.scheduleNextRun(job) + s.runContinuous(job) } return job, nil } +// Do specifies the jobFunc that should be called every time the Job runs +func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) { + return s.doCommon(jobFun, params...) +} + +// DoWithJobDetails specifies the jobFunc that should be called every time the Job runs +// and additionally passes the details of the current job to the jobFunc. +// The last argument of the function must be a gocron.Job that will be passed by +// the scheduler when the function is called. +func (s *Scheduler) DoWithJobDetails(jobFun interface{}, params ...interface{}) (*Job, error) { + job := s.getCurrentJob() + job.runWithDetails = true + job.parametersLen = len(params) + return s.doCommon(jobFun, params...) +} + // At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM" // or time.Time (note that only the hours, minutes, seconds and nanos are used). func (s *Scheduler) At(i interface{}) *Scheduler { @@ -871,6 +1043,15 @@ func (s *Scheduler) Tag(t ...string) *Scheduler { return s } +// GetAllTags returns all tags. +func (s *Scheduler) GetAllTags() []string { + var tags []string + for _, job := range s.Jobs() { + tags = append(tags, job.Tags()...) + } + return tags +} + // 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 { @@ -984,10 +1165,8 @@ func (s *Scheduler) Months(daysOfTheMonth ...int) *Scheduler { job.error = wrapOrError(job.error, ErrInvalidDayOfMonthEntry) } } else { - repeatMap := make(map[int]int) for _, dayOfMonth := range daysOfTheMonth { - if dayOfMonth < 1 || dayOfMonth > 28 { job.error = wrapOrError(job.error, ErrInvalidDayOfMonthEntry) break @@ -1003,9 +1182,8 @@ func (s *Scheduler) Months(daysOfTheMonth ...int) *Scheduler { if _, ok := repeatMap[dayOfMonth]; ok { job.error = wrapOrError(job.error, ErrInvalidDaysOfMonthDuplicateValue) break - } else { - repeatMap[dayOfMonth]++ } + repeatMap[dayOfMonth]++ } } if job.daysOfTheMonth == nil { @@ -1075,11 +1253,17 @@ func (s *Scheduler) Sunday() *Scheduler { } func (s *Scheduler) getCurrentJob() *Job { - if len(s.Jobs()) == 0 { - s.setJobs([]*Job{{}}) - s.jobCreated = true + if !s.inScheduleChain { + s.jobsMutex.Lock() + s.jobs = append(s.jobs, s.newJob(0)) + s.jobsMutex.Unlock() + s.inScheduleChain = true } - return s.Jobs()[len(s.Jobs())-1] + + s.jobsMutex.RLock() + defer s.jobsMutex.RUnlock() + + return s.jobs[len(s.jobs)-1] } func (s *Scheduler) now() time.Time { @@ -1105,6 +1289,7 @@ func (s *Scheduler) Job(j *Job) *Scheduler { s.Swap(len(jobs)-1, index) } } + s.inScheduleChain = true s.updateJob = true return s } @@ -1120,7 +1305,20 @@ func (s *Scheduler) Update() (*Job, error) { } s.updateJob = false job.stop() - job.ctx, job.cancel = context.WithCancel(context.Background()) + job.setStartsImmediately(false) + + if job.runWithDetails { + params := job.parameters + if len(params) > 0 { + params = job.parameters[:len(job.parameters)-1] + } + return s.DoWithJobDetails(job.function, params...) + } + + if job.runConfig.mode == singletonMode { + job.SingletonMode() + } + return s.Do(job.function, job.parameters...) } @@ -1133,12 +1331,14 @@ func (s *Scheduler) CronWithSeconds(cronExpression string) *Scheduler { } func (s *Scheduler) cron(cronExpression string, withSeconds bool) *Scheduler { - job := s.newJob(0) - if s.updateJob || s.jobCreated { - job = s.getCurrentJob() - } + job := s.getCurrentJob() - withLocation := fmt.Sprintf("CRON_TZ=%s %s", s.location.String(), cronExpression) + var withLocation string + if strings.HasPrefix(cronExpression, "TZ=") || strings.HasPrefix(cronExpression, "CRON_TZ=") { + withLocation = cronExpression + } else { + withLocation = fmt.Sprintf("CRON_TZ=%s %s", s.location.String(), cronExpression) + } var ( cronSchedule cron.Schedule @@ -1160,12 +1360,6 @@ func (s *Scheduler) cron(cronExpression string, withSeconds bool) *Scheduler { job.setUnit(crontab) job.startsImmediately = false - if s.updateJob || s.jobCreated { - s.setJobs(append(s.Jobs()[:len(s.Jobs())-1], job)) - s.jobCreated = false - } else { - s.setJobs(append(s.Jobs(), job)) - } return s } @@ -1203,3 +1397,65 @@ func (s *Scheduler) StartImmediately() *Scheduler { job.startsImmediately = true return s } + +// CustomTime takes an in a struct that implements the TimeWrapper interface +// allowing the caller to mock the time used by the scheduler. This is useful +// for tests relying on gocron. +func (s *Scheduler) CustomTime(customTimeWrapper TimeWrapper) { + s.time = customTimeWrapper +} + +// CustomTimer takes in a function that mirrors the time.AfterFunc +// This is used to mock the time.AfterFunc function used by the scheduler +// for testing long intervals in a short amount of time. +func (s *Scheduler) CustomTimer(customTimer func(d time.Duration, f func()) *time.Timer) { + s.timer = customTimer +} + +func (s *Scheduler) StopBlockingChan() { + s.startBlockingStopChanMutex.Lock() + if s.IsRunning() && s.startBlockingStopChan != nil { + close(s.startBlockingStopChan) + } + s.startBlockingStopChanMutex.Unlock() +} + +// WithDistributedLocker prevents the same job from being run more than once +// when multiple schedulers are trying to schedule the same job. +// +// NOTE - This is currently in BETA. Please provide any feedback on your usage +// and open bugs with any issues. +// +// One strategy to reduce splay in the job execution times when using +// intervals (e.g. 1s, 1m, 1h), on each scheduler instance, is to use +// StartAt with time.Now().Round(interval) to start the job at the +// next interval boundary. +// +// Another strategy is to use the Cron or CronWithSeconds methods as they +// use the same behavior described above using StartAt. +// +// NOTE - the Locker will NOT lock jobs using the singleton options: +// SingletonMode, or SingletonModeAll +// +// NOTE - beware of potential race conditions when running the Locker +// with SetMaxConcurrentJobs and WaitMode as jobs are not guaranteed +// to be locked when each scheduler's is below its limit and able +// to run the job. +func (s *Scheduler) WithDistributedLocker(l Locker) { + s.executor.distributedLocker = l +} + +// RegisterEventListeners accepts EventListeners and registers them for all jobs +// in the scheduler at the time this function is called. +// The event listeners are then called at the times described by each listener. +// If a new job is added, an additional call to this method, or the job specific +// version must be executed in order for the new job to trigger event listeners. +func (s *Scheduler) RegisterEventListeners(eventListeners ...EventListener) { + for _, job := range s.Jobs() { + job.RegisterEventListeners(eventListeners...) + } +} + +func (s *Scheduler) PauseJobExecution(shouldPause bool) { + s.executor.skipExecution.Store(shouldPause) +} diff --git a/vendor/github.com/go-co-op/gocron/timeHelper.go b/vendor/github.com/go-co-op/gocron/timeHelper.go deleted file mode 100644 index b5baeb573..000000000 --- a/vendor/github.com/go-co-op/gocron/timeHelper.go +++ /dev/null @@ -1,25 +0,0 @@ -package gocron - -import "time" - -var _ timeWrapper = (*trueTime)(nil) - -type timeWrapper interface { - Now(*time.Location) time.Time - Unix(int64, int64) time.Time - Sleep(time.Duration) -} - -type trueTime struct{} - -func (t *trueTime) Now(location *time.Location) time.Time { - return time.Now().In(location) -} - -func (t *trueTime) Unix(sec int64, nsec int64) time.Time { - return time.Unix(sec, nsec) -} - -func (t *trueTime) Sleep(d time.Duration) { - time.Sleep(d) -} diff --git a/vendor/github.com/go-co-op/gocron/time_helper.go b/vendor/github.com/go-co-op/gocron/time_helper.go new file mode 100644 index 000000000..487a7a2ab --- /dev/null +++ b/vendor/github.com/go-co-op/gocron/time_helper.go @@ -0,0 +1,33 @@ +package gocron + +import "time" + +var _ TimeWrapper = (*trueTime)(nil) + +// TimeWrapper is an interface that wraps the Now, Sleep, and Unix methods of the time package. +// This allows the library and users to mock the time package for testing. +type TimeWrapper interface { + Now(*time.Location) time.Time + Unix(int64, int64) time.Time + Sleep(time.Duration) +} + +type trueTime struct{} + +func (t *trueTime) Now(location *time.Location) time.Time { + return time.Now().In(location) +} + +func (t *trueTime) Unix(sec int64, nsec int64) time.Time { + return time.Unix(sec, nsec) +} + +func (t *trueTime) Sleep(d time.Duration) { + time.Sleep(d) +} + +// afterFunc proxies the time.AfterFunc function. +// This allows it to be mocked for testing. +func afterFunc(d time.Duration, f func()) *time.Timer { + return time.AfterFunc(d, f) +} diff --git a/vendor/go.uber.org/atomic/.codecov.yml b/vendor/go.uber.org/atomic/.codecov.yml new file mode 100644 index 000000000..571116cc3 --- /dev/null +++ b/vendor/go.uber.org/atomic/.codecov.yml @@ -0,0 +1,19 @@ +coverage: + range: 80..100 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 100 # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure + +# Also update COVER_IGNORE_PKGS in the Makefile. +ignore: + - /internal/gen-atomicint/ + - /internal/gen-valuewrapper/ diff --git a/vendor/go.uber.org/atomic/.gitignore b/vendor/go.uber.org/atomic/.gitignore new file mode 100644 index 000000000..2e337a0ed --- /dev/null +++ b/vendor/go.uber.org/atomic/.gitignore @@ -0,0 +1,15 @@ +/bin +.DS_Store +/vendor +cover.html +cover.out +lint.log + +# Binaries +*.test + +# Profiling output +*.prof + +# Output of fossa analyzer +/fossa diff --git a/vendor/go.uber.org/atomic/CHANGELOG.md b/vendor/go.uber.org/atomic/CHANGELOG.md new file mode 100644 index 000000000..38f564e2b --- /dev/null +++ b/vendor/go.uber.org/atomic/CHANGELOG.md @@ -0,0 +1,100 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.9.0] - 2021-07-15 +### Added +- Add `Float64.Swap` to match int atomic operations. +- Add `atomic.Time` type for atomic operations on `time.Time` values. + +[1.9.0]: https://github.com/uber-go/atomic/compare/v1.8.0...v1.9.0 + +## [1.8.0] - 2021-06-09 +### Added +- Add `atomic.Uintptr` type for atomic operations on `uintptr` values. +- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values. + +[1.8.0]: https://github.com/uber-go/atomic/compare/v1.7.0...v1.8.0 + +## [1.7.0] - 2020-09-14 +### Added +- Support JSON serialization and deserialization of primitive atomic types. +- Support Text marshalling and unmarshalling for string atomics. + +### Changed +- Disallow incorrect comparison of atomic values in a non-atomic way. + +### Removed +- Remove dependency on `golang.org/x/{lint, tools}`. + +[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0 + +## [1.6.0] - 2020-02-24 +### Changed +- Drop library dependency on `golang.org/x/{lint, tools}`. + +[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0 + +## [1.5.1] - 2019-11-19 +- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together + causing `CAS` to fail even though the old value matches. + +[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1 + +## [1.5.0] - 2019-10-29 +### Changed +- With Go modules, only the `go.uber.org/atomic` import path is supported now. + If you need to use the old import path, please add a `replace` directive to + your `go.mod`. + +[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0 + +## [1.4.0] - 2019-05-01 +### Added + - Add `atomic.Error` type for atomic operations on `error` values. + +[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0 + +## [1.3.2] - 2018-05-02 +### Added +- Add `atomic.Duration` type for atomic operations on `time.Duration` values. + +[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2 + +## [1.3.1] - 2017-11-14 +### Fixed +- Revert optimization for `atomic.String.Store("")` which caused data races. + +[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1 + +## [1.3.0] - 2017-11-13 +### Added +- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools. + +### Changed +- Optimize `atomic.String.Store("")` by avoiding an allocation. + +[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0 + +## [1.2.0] - 2017-04-12 +### Added +- Shadow `atomic.Value` from `sync/atomic`. + +[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0 + +## [1.1.0] - 2017-03-10 +### Added +- Add atomic `Float64` type. + +### Changed +- Support new `go.uber.org/atomic` import path. + +[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0 + +## [1.0.0] - 2016-07-18 + +- Initial release. + +[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0 diff --git a/vendor/go.uber.org/atomic/LICENSE.txt b/vendor/go.uber.org/atomic/LICENSE.txt new file mode 100644 index 000000000..8765c9fbc --- /dev/null +++ b/vendor/go.uber.org/atomic/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Uber Technologies, Inc. + +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. diff --git a/vendor/go.uber.org/atomic/Makefile b/vendor/go.uber.org/atomic/Makefile new file mode 100644 index 000000000..46c945b32 --- /dev/null +++ b/vendor/go.uber.org/atomic/Makefile @@ -0,0 +1,79 @@ +# Directory to place `go install`ed binaries into. +export GOBIN ?= $(shell pwd)/bin + +GOLINT = $(GOBIN)/golint +GEN_ATOMICINT = $(GOBIN)/gen-atomicint +GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper +STATICCHECK = $(GOBIN)/staticcheck + +GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print) + +# Also update ignore section in .codecov.yml. +COVER_IGNORE_PKGS = \ + go.uber.org/atomic/internal/gen-atomicint \ + go.uber.org/atomic/internal/gen-atomicwrapper + +.PHONY: build +build: + go build ./... + +.PHONY: test +test: + go test -race ./... + +.PHONY: gofmt +gofmt: + $(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) + gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true + @[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false) + +$(GOLINT): + cd tools && go install golang.org/x/lint/golint + +$(STATICCHECK): + cd tools && go install honnef.co/go/tools/cmd/staticcheck + +$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*) + go build -o $@ ./internal/gen-atomicwrapper + +$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*) + go build -o $@ ./internal/gen-atomicint + +.PHONY: golint +golint: $(GOLINT) + $(GOLINT) ./... + +.PHONY: staticcheck +staticcheck: $(STATICCHECK) + $(STATICCHECK) ./... + +.PHONY: lint +lint: gofmt golint staticcheck generatenodirty + +# comma separated list of packages to consider for code coverage. +COVER_PKG = $(shell \ + go list -find ./... | \ + grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \ + paste -sd, -) + +.PHONY: cover +cover: + go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./... + go tool cover -html=cover.out -o cover.html + +.PHONY: generate +generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER) + go generate ./... + +.PHONY: generatenodirty +generatenodirty: + @[ -z "$$(git status --porcelain)" ] || ( \ + echo "Working tree is dirty. Commit your changes first."; \ + git status; \ + exit 1 ) + @make generate + @status=$$(git status --porcelain); \ + [ -z "$$status" ] || ( \ + echo "Working tree is dirty after `make generate`:"; \ + echo "$$status"; \ + echo "Please ensure that the generated code is up-to-date." ) diff --git a/vendor/go.uber.org/atomic/README.md b/vendor/go.uber.org/atomic/README.md new file mode 100644 index 000000000..96b47a1f1 --- /dev/null +++ b/vendor/go.uber.org/atomic/README.md @@ -0,0 +1,63 @@ +# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard] + +Simple wrappers for primitive types to enforce atomic access. + +## Installation + +```shell +$ go get -u go.uber.org/atomic@v1 +``` + +### Legacy Import Path + +As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way +of using this package. If you are using Go modules, this package will fail to +compile with the legacy import path path `github.com/uber-go/atomic`. + +We recommend migrating your code to the new import path but if you're unable +to do so, or if your dependencies are still using the old import path, you +will have to add a `replace` directive to your `go.mod` file downgrading the +legacy import path to an older version. + +``` +replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0 +``` + +You can do so automatically by running the following command. + +```shell +$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0 +``` + +## Usage + +The standard library's `sync/atomic` is powerful, but it's easy to forget which +variables must be accessed atomically. `go.uber.org/atomic` preserves all the +functionality of the standard library, but wraps the primitive types to +provide a safer, more convenient API. + +```go +var atom atomic.Uint32 +atom.Store(42) +atom.Sub(2) +atom.CAS(40, 11) +``` + +See the [documentation][doc] for a complete API specification. + +## Development Status + +Stable. + +--- + +Released under the [MIT License](LICENSE.txt). + +[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg +[doc]: https://godoc.org/go.uber.org/atomic +[ci-img]: https://github.com/uber-go/atomic/actions/workflows/go.yml/badge.svg +[ci]: https://github.com/uber-go/atomic/actions/workflows/go.yml +[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/atomic +[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic +[reportcard]: https://goreportcard.com/report/go.uber.org/atomic diff --git a/vendor/go.uber.org/atomic/bool.go b/vendor/go.uber.org/atomic/bool.go new file mode 100644 index 000000000..209df7bbc --- /dev/null +++ b/vendor/go.uber.org/atomic/bool.go @@ -0,0 +1,81 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" +) + +// Bool is an atomic type-safe wrapper for bool values. +type Bool struct { + _ nocmp // disallow non-atomic comparison + + v Uint32 +} + +var _zeroBool bool + +// NewBool creates a new Bool. +func NewBool(val bool) *Bool { + x := &Bool{} + if val != _zeroBool { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped bool. +func (x *Bool) Load() bool { + return truthy(x.v.Load()) +} + +// Store atomically stores the passed bool. +func (x *Bool) Store(val bool) { + x.v.Store(boolToInt(val)) +} + +// CAS is an atomic compare-and-swap for bool values. +func (x *Bool) CAS(old, new bool) (swapped bool) { + return x.v.CAS(boolToInt(old), boolToInt(new)) +} + +// Swap atomically stores the given bool and returns the old +// value. +func (x *Bool) Swap(val bool) (old bool) { + return truthy(x.v.Swap(boolToInt(val))) +} + +// MarshalJSON encodes the wrapped bool into JSON. +func (x *Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a bool from JSON. +func (x *Bool) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/bool_ext.go b/vendor/go.uber.org/atomic/bool_ext.go new file mode 100644 index 000000000..a2e60e987 --- /dev/null +++ b/vendor/go.uber.org/atomic/bool_ext.go @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go + +func truthy(n uint32) bool { + return n == 1 +} + +func boolToInt(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// Toggle atomically negates the Boolean and returns the previous value. +func (b *Bool) Toggle() (old bool) { + for { + old := b.Load() + if b.CAS(old, !old) { + return old + } + } +} + +// String encodes the wrapped value as a string. +func (b *Bool) String() string { + return strconv.FormatBool(b.Load()) +} diff --git a/vendor/go.uber.org/atomic/doc.go b/vendor/go.uber.org/atomic/doc.go new file mode 100644 index 000000000..ae7390ee6 --- /dev/null +++ b/vendor/go.uber.org/atomic/doc.go @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +// Package atomic provides simple wrappers around numerics to enforce atomic +// access. +package atomic diff --git a/vendor/go.uber.org/atomic/duration.go b/vendor/go.uber.org/atomic/duration.go new file mode 100644 index 000000000..207594f5e --- /dev/null +++ b/vendor/go.uber.org/atomic/duration.go @@ -0,0 +1,82 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "time" +) + +// Duration is an atomic type-safe wrapper for time.Duration values. +type Duration struct { + _ nocmp // disallow non-atomic comparison + + v Int64 +} + +var _zeroDuration time.Duration + +// NewDuration creates a new Duration. +func NewDuration(val time.Duration) *Duration { + x := &Duration{} + if val != _zeroDuration { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped time.Duration. +func (x *Duration) Load() time.Duration { + return time.Duration(x.v.Load()) +} + +// Store atomically stores the passed time.Duration. +func (x *Duration) Store(val time.Duration) { + x.v.Store(int64(val)) +} + +// CAS is an atomic compare-and-swap for time.Duration values. +func (x *Duration) CAS(old, new time.Duration) (swapped bool) { + return x.v.CAS(int64(old), int64(new)) +} + +// Swap atomically stores the given time.Duration and returns the old +// value. +func (x *Duration) Swap(val time.Duration) (old time.Duration) { + return time.Duration(x.v.Swap(int64(val))) +} + +// MarshalJSON encodes the wrapped time.Duration into JSON. +func (x *Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a time.Duration from JSON. +func (x *Duration) UnmarshalJSON(b []byte) error { + var v time.Duration + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/duration_ext.go b/vendor/go.uber.org/atomic/duration_ext.go new file mode 100644 index 000000000..4c18b0a9e --- /dev/null +++ b/vendor/go.uber.org/atomic/duration_ext.go @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +import "time" + +//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go + +// Add atomically adds to the wrapped time.Duration and returns the new value. +func (d *Duration) Add(delta time.Duration) time.Duration { + return time.Duration(d.v.Add(int64(delta))) +} + +// Sub atomically subtracts from the wrapped time.Duration and returns the new value. +func (d *Duration) Sub(delta time.Duration) time.Duration { + return time.Duration(d.v.Sub(int64(delta))) +} + +// String encodes the wrapped value as a string. +func (d *Duration) String() string { + return d.Load().String() +} diff --git a/vendor/go.uber.org/atomic/error.go b/vendor/go.uber.org/atomic/error.go new file mode 100644 index 000000000..3be19c35e --- /dev/null +++ b/vendor/go.uber.org/atomic/error.go @@ -0,0 +1,51 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +// Error is an atomic type-safe wrapper for error values. +type Error struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroError error + +// NewError creates a new Error. +func NewError(val error) *Error { + x := &Error{} + if val != _zeroError { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped error. +func (x *Error) Load() error { + return unpackError(x.v.Load()) +} + +// Store atomically stores the passed error. +func (x *Error) Store(val error) { + x.v.Store(packError(val)) +} diff --git a/vendor/go.uber.org/atomic/error_ext.go b/vendor/go.uber.org/atomic/error_ext.go new file mode 100644 index 000000000..ffe0be21c --- /dev/null +++ b/vendor/go.uber.org/atomic/error_ext.go @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +// atomic.Value panics on nil inputs, or if the underlying type changes. +// Stabilize by always storing a custom struct that we control. + +//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go + +type packedError struct{ Value error } + +func packError(v error) interface{} { + return packedError{v} +} + +func unpackError(v interface{}) error { + if err, ok := v.(packedError); ok { + return err.Value + } + return nil +} diff --git a/vendor/go.uber.org/atomic/float64.go b/vendor/go.uber.org/atomic/float64.go new file mode 100644 index 000000000..8a1367184 --- /dev/null +++ b/vendor/go.uber.org/atomic/float64.go @@ -0,0 +1,77 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "math" +) + +// Float64 is an atomic type-safe wrapper for float64 values. +type Float64 struct { + _ nocmp // disallow non-atomic comparison + + v Uint64 +} + +var _zeroFloat64 float64 + +// NewFloat64 creates a new Float64. +func NewFloat64(val float64) *Float64 { + x := &Float64{} + if val != _zeroFloat64 { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped float64. +func (x *Float64) Load() float64 { + return math.Float64frombits(x.v.Load()) +} + +// Store atomically stores the passed float64. +func (x *Float64) Store(val float64) { + x.v.Store(math.Float64bits(val)) +} + +// Swap atomically stores the given float64 and returns the old +// value. +func (x *Float64) Swap(val float64) (old float64) { + return math.Float64frombits(x.v.Swap(math.Float64bits(val))) +} + +// MarshalJSON encodes the wrapped float64 into JSON. +func (x *Float64) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a float64 from JSON. +func (x *Float64) UnmarshalJSON(b []byte) error { + var v float64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/float64_ext.go b/vendor/go.uber.org/atomic/float64_ext.go new file mode 100644 index 000000000..df36b0107 --- /dev/null +++ b/vendor/go.uber.org/atomic/float64_ext.go @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "math" + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -swap -json -imports math -file=float64.go + +// Add atomically adds to the wrapped float64 and returns the new value. +func (f *Float64) Add(delta float64) float64 { + for { + old := f.Load() + new := old + delta + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float64 and returns the new value. +func (f *Float64) Sub(delta float64) float64 { + return f.Add(-delta) +} + +// CAS is an atomic compare-and-swap for float64 values. +// +// Note: CAS handles NaN incorrectly. NaN != NaN using Go's inbuilt operators +// but CAS allows a stored NaN to compare equal to a passed in NaN. +// This avoids typical CAS loops from blocking forever, e.g., +// +// for { +// old := atom.Load() +// new = f(old) +// if atom.CAS(old, new) { +// break +// } +// } +// +// If CAS did not match NaN to match, then the above would loop forever. +func (f *Float64) CAS(old, new float64) (swapped bool) { + return f.v.CAS(math.Float64bits(old), math.Float64bits(new)) +} + +// String encodes the wrapped value as a string. +func (f *Float64) String() string { + // 'g' is the behavior for floats with %v. + return strconv.FormatFloat(f.Load(), 'g', -1, 64) +} diff --git a/vendor/go.uber.org/atomic/gen.go b/vendor/go.uber.org/atomic/gen.go new file mode 100644 index 000000000..1e9ef4f87 --- /dev/null +++ b/vendor/go.uber.org/atomic/gen.go @@ -0,0 +1,27 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go +//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go +//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go +//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go +//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go diff --git a/vendor/go.uber.org/atomic/int32.go b/vendor/go.uber.org/atomic/int32.go new file mode 100644 index 000000000..640ea36a1 --- /dev/null +++ b/vendor/go.uber.org/atomic/int32.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int32 is an atomic wrapper around int32. +type Int32 struct { + _ nocmp // disallow non-atomic comparison + + v int32 +} + +// NewInt32 creates a new Int32. +func NewInt32(val int32) *Int32 { + return &Int32{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Int32) Load() int32 { + return atomic.LoadInt32(&i.v) +} + +// Add atomically adds to the wrapped int32 and returns the new value. +func (i *Int32) Add(delta int32) int32 { + return atomic.AddInt32(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped int32 and returns the new value. +func (i *Int32) Sub(delta int32) int32 { + return atomic.AddInt32(&i.v, -delta) +} + +// Inc atomically increments the wrapped int32 and returns the new value. +func (i *Int32) Inc() int32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int32 and returns the new value. +func (i *Int32) Dec() int32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int32) CAS(old, new int32) (swapped bool) { + return atomic.CompareAndSwapInt32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int32) Store(val int32) { + atomic.StoreInt32(&i.v, val) +} + +// Swap atomically swaps the wrapped int32 and returns the old value. +func (i *Int32) Swap(val int32) (old int32) { + return atomic.SwapInt32(&i.v, val) +} + +// MarshalJSON encodes the wrapped int32 into JSON. +func (i *Int32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int32. +func (i *Int32) UnmarshalJSON(b []byte) error { + var v int32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int32) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/int64.go b/vendor/go.uber.org/atomic/int64.go new file mode 100644 index 000000000..9ab66b980 --- /dev/null +++ b/vendor/go.uber.org/atomic/int64.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int64 is an atomic wrapper around int64. +type Int64 struct { + _ nocmp // disallow non-atomic comparison + + v int64 +} + +// NewInt64 creates a new Int64. +func NewInt64(val int64) *Int64 { + return &Int64{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Int64) Load() int64 { + return atomic.LoadInt64(&i.v) +} + +// Add atomically adds to the wrapped int64 and returns the new value. +func (i *Int64) Add(delta int64) int64 { + return atomic.AddInt64(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped int64 and returns the new value. +func (i *Int64) Sub(delta int64) int64 { + return atomic.AddInt64(&i.v, -delta) +} + +// Inc atomically increments the wrapped int64 and returns the new value. +func (i *Int64) Inc() int64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int64 and returns the new value. +func (i *Int64) Dec() int64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int64) CAS(old, new int64) (swapped bool) { + return atomic.CompareAndSwapInt64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int64) Store(val int64) { + atomic.StoreInt64(&i.v, val) +} + +// Swap atomically swaps the wrapped int64 and returns the old value. +func (i *Int64) Swap(val int64) (old int64) { + return atomic.SwapInt64(&i.v, val) +} + +// MarshalJSON encodes the wrapped int64 into JSON. +func (i *Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int64. +func (i *Int64) UnmarshalJSON(b []byte) error { + var v int64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int64) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/nocmp.go b/vendor/go.uber.org/atomic/nocmp.go new file mode 100644 index 000000000..a8201cb4a --- /dev/null +++ b/vendor/go.uber.org/atomic/nocmp.go @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +// nocmp is an uncomparable struct. Embed this inside another struct to make +// it uncomparable. +// +// type Foo struct { +// nocmp +// // ... +// } +// +// This DOES NOT: +// +// - Disallow shallow copies of structs +// - Disallow comparison of pointers to uncomparable structs +type nocmp [0]func() diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go new file mode 100644 index 000000000..80df93d09 --- /dev/null +++ b/vendor/go.uber.org/atomic/string.go @@ -0,0 +1,54 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +// String is an atomic type-safe wrapper for string values. +type String struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroString string + +// NewString creates a new String. +func NewString(val string) *String { + x := &String{} + if val != _zeroString { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped string. +func (x *String) Load() string { + if v := x.v.Load(); v != nil { + return v.(string) + } + return _zeroString +} + +// Store atomically stores the passed string. +func (x *String) Store(val string) { + x.v.Store(val) +} diff --git a/vendor/go.uber.org/atomic/string_ext.go b/vendor/go.uber.org/atomic/string_ext.go new file mode 100644 index 000000000..83d92edaf --- /dev/null +++ b/vendor/go.uber.org/atomic/string_ext.go @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go +// Note: No Swap as String wraps Value, which wraps the stdlib sync/atomic.Value which +// only supports Swap as of go1.17: https://github.com/golang/go/issues/39351 + +// String returns the wrapped value. +func (s *String) String() string { + return s.Load() +} + +// MarshalText encodes the wrapped string into a textual form. +// +// This makes it encodable as JSON, YAML, XML, and more. +func (s *String) MarshalText() ([]byte, error) { + return []byte(s.Load()), nil +} + +// UnmarshalText decodes text and replaces the wrapped string with it. +// +// This makes it decodable from JSON, YAML, XML, and more. +func (s *String) UnmarshalText(b []byte) error { + s.Store(string(b)) + return nil +} diff --git a/vendor/go.uber.org/atomic/time.go b/vendor/go.uber.org/atomic/time.go new file mode 100644 index 000000000..33460fc37 --- /dev/null +++ b/vendor/go.uber.org/atomic/time.go @@ -0,0 +1,55 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "time" +) + +// Time is an atomic type-safe wrapper for time.Time values. +type Time struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroTime time.Time + +// NewTime creates a new Time. +func NewTime(val time.Time) *Time { + x := &Time{} + if val != _zeroTime { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped time.Time. +func (x *Time) Load() time.Time { + return unpackTime(x.v.Load()) +} + +// Store atomically stores the passed time.Time. +func (x *Time) Store(val time.Time) { + x.v.Store(packTime(val)) +} diff --git a/vendor/go.uber.org/atomic/time_ext.go b/vendor/go.uber.org/atomic/time_ext.go new file mode 100644 index 000000000..1e3dc978a --- /dev/null +++ b/vendor/go.uber.org/atomic/time_ext.go @@ -0,0 +1,36 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import "time" + +//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go + +func packTime(t time.Time) interface{} { + return t +} + +func unpackTime(v interface{}) time.Time { + if t, ok := v.(time.Time); ok { + return t + } + return time.Time{} +} diff --git a/vendor/go.uber.org/atomic/uint32.go b/vendor/go.uber.org/atomic/uint32.go new file mode 100644 index 000000000..7859a9cc3 --- /dev/null +++ b/vendor/go.uber.org/atomic/uint32.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint32 is an atomic wrapper around uint32. +type Uint32 struct { + _ nocmp // disallow non-atomic comparison + + v uint32 +} + +// NewUint32 creates a new Uint32. +func NewUint32(val uint32) *Uint32 { + return &Uint32{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uint32) Load() uint32 { + return atomic.LoadUint32(&i.v) +} + +// Add atomically adds to the wrapped uint32 and returns the new value. +func (i *Uint32) Add(delta uint32) uint32 { + return atomic.AddUint32(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uint32 and returns the new value. +func (i *Uint32) Sub(delta uint32) uint32 { + return atomic.AddUint32(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uint32 and returns the new value. +func (i *Uint32) Inc() uint32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint32 and returns the new value. +func (i *Uint32) Dec() uint32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint32) CAS(old, new uint32) (swapped bool) { + return atomic.CompareAndSwapUint32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint32) Store(val uint32) { + atomic.StoreUint32(&i.v, val) +} + +// Swap atomically swaps the wrapped uint32 and returns the old value. +func (i *Uint32) Swap(val uint32) (old uint32) { + return atomic.SwapUint32(&i.v, val) +} + +// MarshalJSON encodes the wrapped uint32 into JSON. +func (i *Uint32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint32. +func (i *Uint32) UnmarshalJSON(b []byte) error { + var v uint32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint32) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/uint64.go b/vendor/go.uber.org/atomic/uint64.go new file mode 100644 index 000000000..2f2a7db63 --- /dev/null +++ b/vendor/go.uber.org/atomic/uint64.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint64 is an atomic wrapper around uint64. +type Uint64 struct { + _ nocmp // disallow non-atomic comparison + + v uint64 +} + +// NewUint64 creates a new Uint64. +func NewUint64(val uint64) *Uint64 { + return &Uint64{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uint64) Load() uint64 { + return atomic.LoadUint64(&i.v) +} + +// Add atomically adds to the wrapped uint64 and returns the new value. +func (i *Uint64) Add(delta uint64) uint64 { + return atomic.AddUint64(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uint64 and returns the new value. +func (i *Uint64) Sub(delta uint64) uint64 { + return atomic.AddUint64(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uint64 and returns the new value. +func (i *Uint64) Inc() uint64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint64 and returns the new value. +func (i *Uint64) Dec() uint64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint64) CAS(old, new uint64) (swapped bool) { + return atomic.CompareAndSwapUint64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint64) Store(val uint64) { + atomic.StoreUint64(&i.v, val) +} + +// Swap atomically swaps the wrapped uint64 and returns the old value. +func (i *Uint64) Swap(val uint64) (old uint64) { + return atomic.SwapUint64(&i.v, val) +} + +// MarshalJSON encodes the wrapped uint64 into JSON. +func (i *Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint64. +func (i *Uint64) UnmarshalJSON(b []byte) error { + var v uint64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint64) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/uintptr.go b/vendor/go.uber.org/atomic/uintptr.go new file mode 100644 index 000000000..ecf7a7727 --- /dev/null +++ b/vendor/go.uber.org/atomic/uintptr.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uintptr is an atomic wrapper around uintptr. +type Uintptr struct { + _ nocmp // disallow non-atomic comparison + + v uintptr +} + +// NewUintptr creates a new Uintptr. +func NewUintptr(val uintptr) *Uintptr { + return &Uintptr{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uintptr) Load() uintptr { + return atomic.LoadUintptr(&i.v) +} + +// Add atomically adds to the wrapped uintptr and returns the new value. +func (i *Uintptr) Add(delta uintptr) uintptr { + return atomic.AddUintptr(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uintptr and returns the new value. +func (i *Uintptr) Sub(delta uintptr) uintptr { + return atomic.AddUintptr(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uintptr and returns the new value. +func (i *Uintptr) Inc() uintptr { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uintptr and returns the new value. +func (i *Uintptr) Dec() uintptr { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uintptr) CAS(old, new uintptr) (swapped bool) { + return atomic.CompareAndSwapUintptr(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uintptr) Store(val uintptr) { + atomic.StoreUintptr(&i.v, val) +} + +// Swap atomically swaps the wrapped uintptr and returns the old value. +func (i *Uintptr) Swap(val uintptr) (old uintptr) { + return atomic.SwapUintptr(&i.v, val) +} + +// MarshalJSON encodes the wrapped uintptr into JSON. +func (i *Uintptr) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uintptr. +func (i *Uintptr) UnmarshalJSON(b []byte) error { + var v uintptr + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uintptr) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/unsafe_pointer.go b/vendor/go.uber.org/atomic/unsafe_pointer.go new file mode 100644 index 000000000..169f793dc --- /dev/null +++ b/vendor/go.uber.org/atomic/unsafe_pointer.go @@ -0,0 +1,58 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// 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. + +package atomic + +import ( + "sync/atomic" + "unsafe" +) + +// UnsafePointer is an atomic wrapper around unsafe.Pointer. +type UnsafePointer struct { + _ nocmp // disallow non-atomic comparison + + v unsafe.Pointer +} + +// NewUnsafePointer creates a new UnsafePointer. +func NewUnsafePointer(val unsafe.Pointer) *UnsafePointer { + return &UnsafePointer{v: val} +} + +// Load atomically loads the wrapped value. +func (p *UnsafePointer) Load() unsafe.Pointer { + return atomic.LoadPointer(&p.v) +} + +// Store atomically stores the passed value. +func (p *UnsafePointer) Store(val unsafe.Pointer) { + atomic.StorePointer(&p.v, val) +} + +// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value. +func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) { + return atomic.SwapPointer(&p.v, val) +} + +// CAS is an atomic compare-and-swap. +func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) { + return atomic.CompareAndSwapPointer(&p.v, old, new) +} diff --git a/vendor/go.uber.org/atomic/value.go b/vendor/go.uber.org/atomic/value.go new file mode 100644 index 000000000..671f3a382 --- /dev/null +++ b/vendor/go.uber.org/atomic/value.go @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// 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. + +package atomic + +import "sync/atomic" + +// Value shadows the type of the same name from sync/atomic +// https://godoc.org/sync/atomic#Value +type Value struct { + atomic.Value + + _ nocmp // disallow non-atomic comparison +} diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE deleted file mode 100644 index 6a66aea5e..000000000 --- a/vendor/golang.org/x/sync/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 733099041..000000000 --- a/vendor/golang.org/x/sync/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 30f632c57..000000000 --- a/vendor/golang.org/x/sync/semaphore/semaphore.go +++ /dev/null @@ -1,136 +0,0 @@ -// 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 deleted file mode 100644 index 690eb8501..000000000 --- a/vendor/golang.org/x/sync/singleflight/singleflight.go +++ /dev/null @@ -1,212 +0,0 @@ -// 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/modules.txt b/vendor/modules.txt index a779840c4..5a35797f0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -26,8 +26,8 @@ github.com/evanphx/json-patch # github.com/fsnotify/fsnotify v1.5.4 ## explicit; go 1.16 github.com/fsnotify/fsnotify -# github.com/go-co-op/gocron v1.13.0 -## explicit; go 1.17 +# github.com/go-co-op/gocron v1.31.0 +## explicit; go 1.16 github.com/go-co-op/gocron # github.com/go-logr/logr v1.2.3 ## explicit; go 1.16 @@ -163,6 +163,9 @@ github.com/robfig/cron/v3 # github.com/spf13/pflag v1.0.5 ## explicit; go 1.12 github.com/spf13/pflag +# go.uber.org/atomic v1.9.0 +## explicit; go 1.13 +go.uber.org/atomic # golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 ## explicit; go 1.17 golang.org/x/mod/internal/lazyregexp @@ -183,10 +186,6 @@ golang.org/x/net/idna ## explicit; go 1.11 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 -## explicit -golang.org/x/sync/semaphore -golang.org/x/sync/singleflight # golang.org/x/sys v0.5.0 ## explicit; go 1.17 golang.org/x/sys/execabs