This commit is contained in:
13
.drone.yml
Normal file
13
.drone.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang
|
||||
commands:
|
||||
- go test
|
||||
|
||||
- name: golangci-lint
|
||||
image: golangci/golangci-lint:v1.26
|
||||
commands:
|
||||
- golangci-lint run
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# ---> Go
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
31
.golangci.yml
Normal file
31
.golangci.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0
|
||||
govet:
|
||||
check-shadowing: true
|
||||
lll:
|
||||
line-length: 140
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gomnd
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
MIT License Copyright (c) 2020 4devs
|
||||
|
||||
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 (including the next
|
||||
paragraph) 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.
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# daemon
|
||||
|
||||
[](https://drone.gitoa.ru/go-4devs/daemon)
|
||||
[](https://goreportcard.com/report/gitoa.ru/go-4devs/daemon)
|
||||
[](http://godoc.org/gitoa.ru/go-4devs/daemon)
|
||||
4
doc.go
Normal file
4
doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package daemon for the run job background and manage them.
|
||||
*/
|
||||
package daemon
|
||||
53
error.go
Normal file
53
error.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package daemon
|
||||
|
||||
import "time"
|
||||
|
||||
// StopJob stop job.
|
||||
func StopJob(err error) error {
|
||||
return &stop{e: err}
|
||||
}
|
||||
|
||||
// IsStoppedJob check stopped job.
|
||||
func IsStoppedJob(err error) bool {
|
||||
_, ok := err.(*stop)
|
||||
return ok
|
||||
}
|
||||
|
||||
type stop struct {
|
||||
e error
|
||||
}
|
||||
|
||||
// Is check type.
|
||||
func (s *stop) Is(err error) bool {
|
||||
_, ok := err.(*stop)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Error base error interface.
|
||||
func (s *stop) Error() string {
|
||||
return s.e.Error()
|
||||
}
|
||||
|
||||
// GetDelayedJob get delay job.
|
||||
func GetDelayedJob(err error) (time.Duration, bool) {
|
||||
if d, ok := err.(*delay); ok {
|
||||
return d.d, ok
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// DelayJob update delay next Run job.
|
||||
func DelayJob(d time.Duration, err error) error {
|
||||
return &delay{d: d, e: err}
|
||||
}
|
||||
|
||||
type delay struct {
|
||||
d time.Duration
|
||||
e error
|
||||
}
|
||||
|
||||
// Error base error interface.
|
||||
func (d *delay) Error() string {
|
||||
return d.e.Error()
|
||||
}
|
||||
219
job.go
Normal file
219
job.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Option configure job.
|
||||
type Option func(*Job)
|
||||
|
||||
// Job run job by frequency.
|
||||
type Job struct {
|
||||
name string
|
||||
run Run
|
||||
stop Run
|
||||
sem chan struct{}
|
||||
err chan error
|
||||
timer Timer
|
||||
delay time.Duration
|
||||
freq func(time.Time) time.Duration
|
||||
handleErr func(error)
|
||||
}
|
||||
|
||||
// WithName sets job name.
|
||||
func WithName(name string) Option {
|
||||
return func(j *Job) { j.name = name }
|
||||
}
|
||||
|
||||
// WithStop sets stop handle for job.
|
||||
func WithStop(stop Run) Option {
|
||||
return func(j *Job) { j.stop = stop }
|
||||
}
|
||||
|
||||
// WithTimer sets time,r to job.
|
||||
func WithTimer(timer Timer) Option {
|
||||
return func(j *Job) { j.timer = timer }
|
||||
}
|
||||
|
||||
// WithDelay sets delay Run job.
|
||||
func WithDelay(delay time.Duration) Option {
|
||||
return func(j *Job) {
|
||||
j.timer.Reset(delay)
|
||||
j.delay = delay
|
||||
}
|
||||
}
|
||||
|
||||
// WithFreq sets frequency Run job..
|
||||
func WithFreq(freq time.Duration) Option {
|
||||
return func(j *Job) {
|
||||
j.freq = func(time.Time) time.Duration {
|
||||
return freq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSchedule set delay and frequency Run job.
|
||||
func WithSchedule(next func(time.Time) time.Duration) Option {
|
||||
return func(j *Job) {
|
||||
j.freq = next
|
||||
j.delay = next(time.Now())
|
||||
j.timer.Reset(j.delay)
|
||||
}
|
||||
}
|
||||
|
||||
// WithRunMiddleware added middleware for the run job.
|
||||
func WithRunMiddleware(fn ...Handle) Option {
|
||||
return func(j *Job) {
|
||||
run := j.run
|
||||
j.run = func(ctx context.Context) error {
|
||||
return chain(fn...)(ctx, run)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithStopMiddleware added middleware for the stop job.
|
||||
func WithStopMiddleware(fn ...Handle) Option {
|
||||
return func(j *Job) {
|
||||
stop := j.stop
|
||||
j.stop = func(ctx context.Context) error {
|
||||
return chain(fn...)(ctx, stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHandleErr add error hanler.
|
||||
func WithHandleErr(fn func(error)) Option {
|
||||
return func(j *Job) {
|
||||
j.handleErr = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Run init function for the change state.
|
||||
type Run func(ctx context.Context) error
|
||||
|
||||
// Handle middleware interface.
|
||||
type Handle func(ctx context.Context, next Run) error
|
||||
|
||||
func stopJob(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleErr(error) {}
|
||||
|
||||
// NewJob creates new job.
|
||||
func NewJob(run Run, opts ...Option) *Job {
|
||||
j := Job{
|
||||
delay: time.Nanosecond,
|
||||
sem: make(chan struct{}, 1),
|
||||
err: make(chan error, 1),
|
||||
timer: NewTicker(time.Nanosecond),
|
||||
freq: func(time.Time) time.Duration {
|
||||
return time.Second
|
||||
},
|
||||
name: getFuncName(run),
|
||||
stop: stopJob,
|
||||
run: run,
|
||||
handleErr: handleErr,
|
||||
}
|
||||
j = j.With(opts...)
|
||||
|
||||
return &j
|
||||
}
|
||||
|
||||
// HandleErr handle returned error.
|
||||
func (j *Job) HandleErr(err error) {
|
||||
j.handleErr(err)
|
||||
}
|
||||
|
||||
// Do run job.
|
||||
func (j *Job) Do(ctx context.Context) <-chan error {
|
||||
<-j.timer.Tick()
|
||||
j.sem <- struct{}{}
|
||||
err := j.run(ctx)
|
||||
<-j.sem
|
||||
|
||||
switch tr := err.(type) {
|
||||
case *stop:
|
||||
case *delay:
|
||||
j.timer.Reset(tr.d)
|
||||
default:
|
||||
j.timer.Reset(j.freq(time.Now()))
|
||||
}
|
||||
j.err <- err
|
||||
|
||||
return j.err
|
||||
}
|
||||
|
||||
// Stop job.
|
||||
func (j *Job) Stop(ctx context.Context) error {
|
||||
return j.stop(ctx)
|
||||
}
|
||||
|
||||
// String gets job name.
|
||||
func (j *Job) String() string {
|
||||
return j.name
|
||||
}
|
||||
|
||||
// With configure job.
|
||||
func (j Job) With(opts ...Option) Job {
|
||||
for _, o := range opts {
|
||||
o(&j)
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
func getFuncName(i interface{}) string {
|
||||
callerName := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
if callerName == "" {
|
||||
return "-"
|
||||
}
|
||||
|
||||
callerName = strings.NewReplacer("(*", "", ")", "").Replace(callerName)
|
||||
lastIndex := strings.LastIndex(callerName, "/")
|
||||
|
||||
if lastIndex != -1 {
|
||||
callerName = callerName[lastIndex+1:]
|
||||
}
|
||||
|
||||
return callerName
|
||||
}
|
||||
|
||||
func chain(handleFunc ...Handle) Handle {
|
||||
n := len(handleFunc)
|
||||
if n > 1 {
|
||||
lastI := n - 1
|
||||
|
||||
return func(ctx context.Context, next Run) error {
|
||||
var (
|
||||
chainHandler Run
|
||||
curI int
|
||||
)
|
||||
|
||||
chainHandler = func(currentCtx context.Context) error {
|
||||
if curI == lastI {
|
||||
return next(currentCtx)
|
||||
}
|
||||
curI++
|
||||
err := handleFunc[curI](currentCtx, chainHandler)
|
||||
curI--
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return handleFunc[0](ctx, chainHandler)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return handleFunc[0]
|
||||
}
|
||||
|
||||
return func(ctx context.Context, next Run) error {
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
62
manager.go
Normal file
62
manager.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Manager run jobs.
|
||||
type Manager struct {
|
||||
sync.WaitGroup
|
||||
close chan struct{}
|
||||
opts []Option
|
||||
}
|
||||
|
||||
// New creates new manager and configure them.
|
||||
func New(opts ...Option) *Manager {
|
||||
m := &Manager{
|
||||
opts: opts,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Do runs job.
|
||||
func (m *Manager) Do(ctx context.Context, j *Job, opts ...Option) {
|
||||
m.Add(1)
|
||||
job := j.With(append(m.opts, opts...)...)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
j.HandleErr(job.Stop(ctx))
|
||||
m.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.close:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-job.Do(ctx):
|
||||
if err != nil {
|
||||
if errors.Is(err, &stop{}) {
|
||||
return
|
||||
}
|
||||
|
||||
j.HandleErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Close jobs.
|
||||
func (m *Manager) Close() error {
|
||||
close(m.close)
|
||||
m.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
260
manager_example_test.go
Normal file
260
manager_example_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package daemon_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/daemon"
|
||||
)
|
||||
|
||||
var ErrJob = errors.New("some reason")
|
||||
|
||||
func ExampleManager() {
|
||||
m := daemon.New()
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
// do some job
|
||||
return daemon.StopJob(nil)
|
||||
}, daemon.WithName("awesome job"))
|
||||
|
||||
m.Do(context.Background(), j,
|
||||
// set frequency run job
|
||||
daemon.WithFreq(time.Minute),
|
||||
// set delay for first run job
|
||||
daemon.WithDelay(time.Second),
|
||||
// set handler if run job return err
|
||||
daemon.WithHandleErr(func(err error) {
|
||||
log.Println(err)
|
||||
}),
|
||||
)
|
||||
m.Wait()
|
||||
// Output:
|
||||
}
|
||||
|
||||
func ExampleManager_withClose() {
|
||||
m := daemon.New()
|
||||
|
||||
defer func() {
|
||||
_ = m.Close()
|
||||
}()
|
||||
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
fmt.Println("do some job;")
|
||||
return daemon.StopJob(nil)
|
||||
}, daemon.WithName("awesome job"))
|
||||
|
||||
m.Do(context.Background(), j, daemon.WithFreq(time.Microsecond))
|
||||
// some blocked process
|
||||
time.Sleep(time.Second)
|
||||
// Output: do some job;
|
||||
}
|
||||
|
||||
func ExampleManager_withOptions() {
|
||||
ctx := context.Background()
|
||||
|
||||
middlewareRun := func(ctx context.Context, next daemon.Run) error {
|
||||
fmt.Println("do some before run all job;")
|
||||
|
||||
err := next(ctx)
|
||||
|
||||
fmt.Println("do some after run all job;")
|
||||
|
||||
return err
|
||||
}
|
||||
middlewareStop := func(ctx context.Context, next daemon.Run) error {
|
||||
fmt.Println("do some before close all job;")
|
||||
|
||||
err := next(ctx)
|
||||
|
||||
fmt.Println("do some after close all job;")
|
||||
|
||||
return err
|
||||
}
|
||||
m := daemon.New(
|
||||
daemon.WithRunMiddleware(middlewareRun),
|
||||
daemon.WithStopMiddleware(middlewareStop),
|
||||
daemon.WithHandleErr(func(err error) {
|
||||
// do some if close return err
|
||||
log.Println(err)
|
||||
}),
|
||||
)
|
||||
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
fmt.Println("do some job;")
|
||||
return daemon.StopJob(nil)
|
||||
}, daemon.WithName("awesome job"))
|
||||
j2 := daemon.NewJob(func(ctx context.Context) error {
|
||||
fmt.Println("do some job2;")
|
||||
return daemon.StopJob(nil)
|
||||
}, daemon.WithName("awesome job2"))
|
||||
|
||||
m.Do(ctx, j, daemon.WithFreq(time.Minute), daemon.WithDelay(time.Second))
|
||||
m.Do(ctx, j2, daemon.WithFreq(time.Nanosecond))
|
||||
m.Wait()
|
||||
// Output:
|
||||
// do some before run all job;
|
||||
// do some job2;
|
||||
// do some after run all job;
|
||||
// do some before close all job;
|
||||
// do some after close all job;
|
||||
// do some before run all job;
|
||||
// do some job;
|
||||
// do some after run all job;
|
||||
// do some before close all job;
|
||||
// do some after close all job;
|
||||
}
|
||||
|
||||
func ExampleNewJob() {
|
||||
ctx := context.Background()
|
||||
m := daemon.New(func(j *daemon.Job) {
|
||||
daemon.WithRunMiddleware(func(ctx context.Context, next daemon.Run) error {
|
||||
fmt.Printf("running job: %s\n", j)
|
||||
return daemon.StopJob(next(ctx))
|
||||
})(j)
|
||||
})
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
// do some
|
||||
return nil
|
||||
}, daemon.WithName("my awesome job"))
|
||||
|
||||
m.Do(ctx, j)
|
||||
|
||||
m.Wait()
|
||||
// Output: running job: my awesome job
|
||||
}
|
||||
|
||||
func ExampleNewJob_withClose() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
fmt.Println("do some long job;")
|
||||
return daemon.StopJob(nil)
|
||||
}, daemon.WithStop(func(ctx context.Context) error {
|
||||
fmt.Println("do some close job;")
|
||||
return nil
|
||||
}))
|
||||
|
||||
m.Do(ctx, j)
|
||||
|
||||
m.Wait()
|
||||
// Output:
|
||||
// do some long job;
|
||||
// do some close job;
|
||||
}
|
||||
|
||||
func ExampleJob_withMiddleware() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
j := daemon.NewJob(
|
||||
func(ctx context.Context) error {
|
||||
fmt.Println("do some job;")
|
||||
return daemon.StopJob(nil)
|
||||
},
|
||||
daemon.WithStop(func(ctx context.Context) error {
|
||||
fmt.Println("do some close job;")
|
||||
return nil
|
||||
}),
|
||||
daemon.WithRunMiddleware(func(ctx context.Context, next daemon.Run) error {
|
||||
fmt.Println("do some before run func;")
|
||||
err := next(ctx)
|
||||
fmt.Println("do some after run func;")
|
||||
return err
|
||||
}),
|
||||
daemon.WithStopMiddleware(func(ctx context.Context, next daemon.Run) error {
|
||||
fmt.Println("do some before close func;")
|
||||
err := next(ctx)
|
||||
fmt.Println("do some after close func;")
|
||||
return err
|
||||
}),
|
||||
)
|
||||
|
||||
m.Do(ctx, j)
|
||||
|
||||
m.Wait()
|
||||
// Output:
|
||||
// do some before run func;
|
||||
// do some job;
|
||||
// do some after run func;
|
||||
// do some before close func;
|
||||
// do some close job;
|
||||
// do some after close func;
|
||||
}
|
||||
|
||||
func ExampleJob_option() {
|
||||
var cnt int32
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
m := daemon.New()
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
if cnt == 2 {
|
||||
return daemon.StopJob(nil)
|
||||
}
|
||||
atomic.AddInt32(&cnt, 1)
|
||||
fmt.Println("do some")
|
||||
return nil
|
||||
},
|
||||
// set freq run job
|
||||
daemon.WithFreq(time.Microsecond),
|
||||
// set delay to start job
|
||||
daemon.WithDelay(time.Nanosecond),
|
||||
)
|
||||
|
||||
m.Do(ctx, j)
|
||||
m.Wait()
|
||||
// Output:
|
||||
//do some
|
||||
//do some
|
||||
}
|
||||
|
||||
func ExampleNewJob_stop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
var i int32
|
||||
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
atomic.AddInt32(&i, 1)
|
||||
fmt.Print("do some:", i, " ")
|
||||
return daemon.StopJob(ErrJob)
|
||||
})
|
||||
|
||||
m.Do(ctx, j)
|
||||
|
||||
m.Wait()
|
||||
// Output: do some:1
|
||||
}
|
||||
|
||||
func ExampleJob_delay() {
|
||||
var i int32
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
m := daemon.New()
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
if i == 3 {
|
||||
return daemon.StopJob(nil)
|
||||
}
|
||||
atomic.AddInt32(&i, 1)
|
||||
fmt.Print("do some:", i, " ")
|
||||
return daemon.DelayJob(time.Second/2, ErrJob)
|
||||
})
|
||||
|
||||
m.Do(ctx, j)
|
||||
|
||||
m.Wait()
|
||||
// Output: do some:1 do some:2 do some:3
|
||||
}
|
||||
164
manager_test.go
Normal file
164
manager_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package daemon_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/daemon"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var (
|
||||
ctx = context.Background()
|
||||
errJob = errors.New("error job")
|
||||
jobName = "job name"
|
||||
jobNameOption = daemon.WithName(jobName)
|
||||
)
|
||||
|
||||
func createJob(count *int32, d time.Duration, err error, opts ...daemon.Option) *daemon.Job {
|
||||
opts = append(opts, jobNameOption)
|
||||
|
||||
return daemon.NewJob(func(ctx context.Context) error {
|
||||
if d > 0 {
|
||||
<-time.After(d)
|
||||
}
|
||||
atomic.AddInt32(count, 1)
|
||||
return err
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
func TestDoJobSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
cnt int32
|
||||
cnt2 int32
|
||||
runm int32
|
||||
clm int32
|
||||
)
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
m.Do(ctx, createJob(&cnt, 0, nil), daemon.WithFreq(time.Second/3))
|
||||
|
||||
mwRun := func(ctx context.Context, next daemon.Run) error {
|
||||
atomic.AddInt32(&runm, 1)
|
||||
return next(ctx)
|
||||
}
|
||||
mwStop := func(ctx context.Context, next daemon.Run) error {
|
||||
atomic.AddInt32(&clm, 1)
|
||||
return next(ctx)
|
||||
}
|
||||
m.Do(ctx, createJob(&cnt2, 0, nil,
|
||||
daemon.WithRunMiddleware(mwRun, mwRun, mwRun),
|
||||
daemon.WithStopMiddleware(mwStop, mwStop, mwStop),
|
||||
), daemon.WithFreq(time.Second/10))
|
||||
time.AfterFunc(time.Second, func() {
|
||||
requireNil(t, m.Close())
|
||||
})
|
||||
m.Wait()
|
||||
|
||||
requireTrue(t, cnt > 2)
|
||||
requireTrue(t, cnt2 >= 10)
|
||||
requireTrue(t, cnt2*3 == runm)
|
||||
requireTrue(t, clm == 3)
|
||||
}
|
||||
|
||||
func requireNil(t *testing.T, ex interface{}) {
|
||||
t.Helper()
|
||||
|
||||
if ex != nil {
|
||||
t.Fatal("expect nil")
|
||||
}
|
||||
}
|
||||
|
||||
func requireTrue(t *testing.T, ex bool) {
|
||||
t.Helper()
|
||||
|
||||
if !ex {
|
||||
t.Fatal("expect true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoJobStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
var cnt int32
|
||||
|
||||
m.Do(ctx, createJob(&cnt, 0, daemon.StopJob(nil)), daemon.WithFreq(time.Nanosecond))
|
||||
m.Wait()
|
||||
requireTrue(t, cnt == 1)
|
||||
}
|
||||
|
||||
func TestDoJobDelay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cnt int32
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
m.Do(ctx, createJob(&cnt, 0, daemon.DelayJob(time.Second/3, nil)), daemon.WithFreq(time.Second))
|
||||
time.AfterFunc(time.Second, func() {
|
||||
requireNil(t, m.Close())
|
||||
})
|
||||
m.Wait()
|
||||
requireTrue(t, cnt > 2)
|
||||
}
|
||||
|
||||
func TestDoJobSkipErr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cnt int32
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
m.Do(ctx, createJob(&cnt, 0, errJob), daemon.WithFreq(time.Second/3))
|
||||
time.AfterFunc(time.Second, func() {
|
||||
requireNil(t, m.Close())
|
||||
})
|
||||
m.Wait()
|
||||
requireTrue(t, cnt > 2)
|
||||
}
|
||||
|
||||
func TestDoJobRetryErr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cnt int32
|
||||
|
||||
m := daemon.New()
|
||||
|
||||
m.Do(ctx, createJob(&cnt, time.Millisecond, errJob, daemon.Retry(3, daemon.StopJob)), daemon.WithFreq(time.Nanosecond))
|
||||
m.Wait()
|
||||
requireTrue(t, cnt == 3)
|
||||
}
|
||||
|
||||
func TestDoJobName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
j := daemon.NewJob(func(ctx context.Context) error {
|
||||
return nil
|
||||
})
|
||||
requireTrue(t, j.String() == "daemon_test.TestDoJobName.func1")
|
||||
|
||||
jn := daemon.NewJob(func(ctx context.Context) error {
|
||||
return nil
|
||||
}, jobNameOption)
|
||||
requireTrue(t, jn.String() == "job name")
|
||||
}
|
||||
|
||||
func TestDoManagerRetryErr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cnt int32
|
||||
|
||||
m := daemon.New(daemon.Retry(3, daemon.StopJob))
|
||||
|
||||
m.Do(ctx, createJob(&cnt, 0, errJob), daemon.WithFreq(time.Second/5))
|
||||
m.Wait()
|
||||
requireTrue(t, cnt == 3)
|
||||
}
|
||||
31
middleware.go
Normal file
31
middleware.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package daemon
|
||||
|
||||
import "context"
|
||||
|
||||
// Retry set retry job and change return after max retry.
|
||||
func Retry(max uint8, handleRetry func(err error) error) Option {
|
||||
var retry uint8
|
||||
|
||||
return func(j *Job) {
|
||||
WithRunMiddleware(func(ctx context.Context, next Run) error {
|
||||
if err := next(ctx); err != nil {
|
||||
retry++
|
||||
if retry >= max {
|
||||
return handleRetry(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
retry = 0
|
||||
return nil
|
||||
})(j)
|
||||
}
|
||||
}
|
||||
|
||||
// RunOnce run once and stopped job.
|
||||
func RunOnce() Option {
|
||||
return func(j *Job) {
|
||||
WithRunMiddleware(func(ctx context.Context, next Run) error {
|
||||
return StopJob(next(ctx))
|
||||
})(j)
|
||||
}
|
||||
}
|
||||
42
timer.go
Normal file
42
timer.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package daemon
|
||||
|
||||
import "time"
|
||||
|
||||
// Timer for the Run job.
|
||||
type Timer interface {
|
||||
Tick() <-chan time.Time
|
||||
Reset(d time.Duration)
|
||||
Stop()
|
||||
}
|
||||
|
||||
// NewTicker create new ticker based on time.ticker.
|
||||
func NewTicker(freq time.Duration) Timer {
|
||||
return &ticker{
|
||||
freq: freq,
|
||||
ticker: time.NewTicker(freq),
|
||||
}
|
||||
}
|
||||
|
||||
type ticker struct {
|
||||
freq time.Duration
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
// Tick time.
|
||||
func (t *ticker) Tick() <-chan time.Time {
|
||||
return t.ticker.C
|
||||
}
|
||||
|
||||
// Stop timer.
|
||||
func (t *ticker) Stop() {
|
||||
t.ticker.Stop()
|
||||
}
|
||||
|
||||
// Reset timer.
|
||||
func (t *ticker) Reset(freq time.Duration) {
|
||||
if t.freq != freq && freq > 0 {
|
||||
t.ticker.Stop()
|
||||
t.freq = freq
|
||||
t.ticker = time.NewTicker(freq)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user