andrey1s
4 years ago
commit
e80be6c6cb
14 changed files with 923 additions and 0 deletions
@ -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 |
@ -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/ |
||||
|
|
@ -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 |
@ -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. |
@ -0,0 +1,5 @@ |
|||||
|
# daemon |
||||
|
|
||||
|
[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/daemon/status.svg)](https://drone.gitoa.ru/go-4devs/daemon) |
||||
|
[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/daemon)](https://goreportcard.com/report/gitoa.ru/go-4devs/daemon) |
||||
|
[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/daemon?status.svg)](http://godoc.org/gitoa.ru/go-4devs/daemon) |
@ -0,0 +1,4 @@ |
|||||
|
/* |
||||
|
Package daemon for the run job background and manage them. |
||||
|
*/ |
||||
|
package daemon |
@ -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() |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
module gitoa.ru/go-4devs/daemon |
||||
|
|
||||
|
go 1.14 |
@ -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) |
||||
|
} |
||||
|
} |
@ -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 |
||||
|
} |
@ -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
|
||||
|
} |
@ -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) |
||||
|
} |
@ -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) |
||||
|
} |
||||
|
} |
@ -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) |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue