Release.
Auto listen OS signal (with mixin) Policies: ...Fail -> ...Error
This commit is contained in:
parent
741cf397a9
commit
01eeeaf5e1
7 changed files with 76 additions and 40 deletions
36
README.md
36
README.md
|
@ -7,7 +7,7 @@ Package Rutina (russian "рутина" - ordinary boring everyday work) is routi
|
||||||
It seems like https://godoc.org/golang.org/x/sync/errgroup with some different:
|
It seems like https://godoc.org/golang.org/x/sync/errgroup with some different:
|
||||||
|
|
||||||
1) propagates context to every routines. So routine can check if context stopped (`ctx.Done()`).
|
1) propagates context to every routines. So routine can check if context stopped (`ctx.Done()`).
|
||||||
2) has flexible run/stop policy. i.e. one routine restarts when it fails (useful on daemons) but if fails another - all routines will be cancelled
|
2) has flexible run/stop policy. i.e. one routine restarts when it errors (useful on daemons) but if errors another - all routines will be cancelled
|
||||||
3) already has optional signal handler `ListenOsSignals()`
|
3) already has optional signal handler `ListenOsSignals()`
|
||||||
|
|
||||||
## When it need?
|
## When it need?
|
||||||
|
@ -42,16 +42,16 @@ r.Go(func (ctx context.Context) error {
|
||||||
|
|
||||||
Available options of run policy:
|
Available options of run policy:
|
||||||
|
|
||||||
* `ShutdownIfFail` - Shutdown all routines if this routine fails
|
* `ShutdownIfError` - Shutdown all routines if this routine returns error
|
||||||
* `RestartIfFail` - Restart this routine if it fail
|
* `RestartIfError` - Restart this routine if this routine returns error
|
||||||
* `DoNothingIfFail` - Do nothing just stop this routine if it fail
|
* `DoNothingIfError` - Do nothing just stop this routine if this routine returns error
|
||||||
* `ShutdownIfDone` - Shutdown all routines if this routine done without errors
|
* `ShutdownIfDone` - Shutdown all routines if this routine done without errors
|
||||||
* `RestartIfDone` - Restart if this routine done without errors
|
* `RestartIfDone` - Restart if this routine done without errors
|
||||||
* `DoNothingIfDone` - Do nothing if this routine done without errors
|
* `DoNothingIfDone` - Do nothing if this routine done without errors
|
||||||
|
|
||||||
Default policy:
|
Default policy:
|
||||||
|
|
||||||
`ShutdownIfFail` && `ShutdownIfDone`
|
`ShutdownIfError` && `ShutdownIfDone`
|
||||||
|
|
||||||
(just like [errgroup](https://godoc.org/golang.org/x/sync/errgroup))
|
(just like [errgroup](https://godoc.org/golang.org/x/sync/errgroup))
|
||||||
|
|
||||||
|
@ -61,17 +61,17 @@ Default policy:
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
// If this routine produce no error - it just restarts
|
// If this routine produce no error - it just restarts
|
||||||
// If it returns error - all other routines will shutdown (because context cancels)
|
// If it returns error - all other routines will shutdown (because context cancels)
|
||||||
}, rutina.RestartIfDone, rutina.ShutdownIfFail)
|
}, rutina.RestartIfDone, rutina.ShutdownIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
// If this routine produce no error - it just completes
|
// If this routine produce no error - it just completes
|
||||||
// If it returns error - all other routines will shutdown (because context cancels)
|
// If it returns error - all other routines will shutdown (because context cancels)
|
||||||
}, rutina.DoNothingIfDone, rutina.ShutdownIfFail)
|
}, rutina.DoNothingIfDone, rutina.ShutdownIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
// If this routine produce no error - all other routines will shutdown (because context cancels)
|
// If this routine produce no error - all other routines will shutdown (because context cancels)
|
||||||
// If it returns error - it will be restarted
|
// If it returns error - it will be restarted
|
||||||
}, rutina.RestartIfFail)
|
}, rutina.RestartIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
// If this routine stopped by any case - all other routines will shutdown (because context cancels)
|
// If this routine stopped by any case - all other routines will shutdown (because context cancels)
|
||||||
|
@ -103,10 +103,10 @@ Rutina has own simple lifecycle events:
|
||||||
* `EventRoutineStart` - Fires when starts new routine
|
* `EventRoutineStart` - Fires when starts new routine
|
||||||
* `EventRoutineStop` - Fires when routine stopped with any result
|
* `EventRoutineStop` - Fires when routine stopped with any result
|
||||||
* `EventRoutineComplete` - Fires when routine stopped without errors
|
* `EventRoutineComplete` - Fires when routine stopped without errors
|
||||||
* `EventRoutineFail` - Fires when routine stopped with error
|
* `EventRoutineError` - Fires when routine stopped with error
|
||||||
* `EventAppStop` - Fires when all routines stopped with any result
|
* `EventAppStop` - Fires when all routines stopped with any result
|
||||||
* `EventAppComplete` - Fires when all routines stopped with no errors
|
* `EventAppComplete` - Fires when all routines stopped with no errors
|
||||||
* `EventAppFail` - Fires when all routines stopped with error
|
* `EventAppError` - Fires when all routines stopped with error
|
||||||
|
|
||||||
## Mixins
|
## Mixins
|
||||||
|
|
||||||
|
@ -151,6 +151,22 @@ err := <- r.Errors()
|
||||||
|
|
||||||
Turn on errors channel
|
Turn on errors channel
|
||||||
|
|
||||||
|
### Lifecycle listener
|
||||||
|
|
||||||
|
```go
|
||||||
|
r = r.With(rutina.WithLifecycleListener(func (event rutina.Event, rid int) { ... }))
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple lifecycle listener
|
||||||
|
|
||||||
|
### Auto listen OS signals
|
||||||
|
|
||||||
|
```go
|
||||||
|
r = r.With(rutina.WithListenOsSignals())
|
||||||
|
```
|
||||||
|
|
||||||
|
Automatically listen OS signals. There is no `r.ListenOsSignals()` needed.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
HTTP server with graceful shutdown [`example/http_server.go`](https://github.com/NeonXP/rutina/blob/master/example/http_server.go)
|
HTTP server with graceful shutdown [`example/http_server.go`](https://github.com/NeonXP/rutina/blob/master/example/http_server.go)
|
||||||
|
|
|
@ -11,15 +11,15 @@ func _() {
|
||||||
_ = x[EventRoutineStart-0]
|
_ = x[EventRoutineStart-0]
|
||||||
_ = x[EventRoutineStop-1]
|
_ = x[EventRoutineStop-1]
|
||||||
_ = x[EventRoutineComplete-2]
|
_ = x[EventRoutineComplete-2]
|
||||||
_ = x[EventRoutineFail-3]
|
_ = x[EventRoutineError-3]
|
||||||
_ = x[EventAppStop-4]
|
_ = x[EventAppStop-4]
|
||||||
_ = x[EventAppComplete-5]
|
_ = x[EventAppComplete-5]
|
||||||
_ = x[EventAppFail-6]
|
_ = x[EventAppError-6]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Event_name = "EventRoutineStartEventRoutineStopEventRoutineCompleteEventRoutineFailEventAppStopEventAppCompleteEventAppFail"
|
const _Event_name = "EventRoutineStartEventRoutineStopEventRoutineCompleteEventRoutineErrorEventAppStopEventAppCompleteEventAppError"
|
||||||
|
|
||||||
var _Event_index = [...]uint8{0, 17, 33, 53, 69, 81, 97, 109}
|
var _Event_index = [...]uint8{0, 17, 33, 53, 70, 82, 98, 111}
|
||||||
|
|
||||||
func (i Event) String() string {
|
func (i Event) String() string {
|
||||||
if i < 0 || i >= Event(len(_Event_index)-1) {
|
if i < 0 || i >= Event(len(_Event_index)-1) {
|
||||||
|
|
|
@ -8,10 +8,10 @@ const (
|
||||||
EventRoutineStart Event = iota
|
EventRoutineStart Event = iota
|
||||||
EventRoutineStop
|
EventRoutineStop
|
||||||
EventRoutineComplete
|
EventRoutineComplete
|
||||||
EventRoutineFail
|
EventRoutineError
|
||||||
EventAppStop
|
EventAppStop
|
||||||
EventAppComplete
|
EventAppComplete
|
||||||
EventAppFail
|
EventAppError
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hook is function that calls when event fired
|
// Hook is function that calls when event fired
|
||||||
|
|
|
@ -21,31 +21,31 @@ func main() {
|
||||||
<-time.After(1 * time.Second)
|
<-time.After(1 * time.Second)
|
||||||
log.Println("Do something 1 second without errors and restart")
|
log.Println("Do something 1 second without errors and restart")
|
||||||
return nil
|
return nil
|
||||||
}, rutina.RestartIfDone, rutina.ShutdownIfFail)
|
}, rutina.RestartIfDone, rutina.ShutdownIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
<-time.After(2 * time.Second)
|
<-time.After(2 * time.Second)
|
||||||
log.Println("Do something 2 seconds without errors and do nothing")
|
log.Println("Do something 2 seconds without errors and do nothing")
|
||||||
return nil
|
return nil
|
||||||
}, rutina.DoNothingIfDone, rutina.ShutdownIfFail)
|
}, rutina.DoNothingIfDone, rutina.ShutdownIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
<-time.After(3 * time.Second)
|
<-time.After(3 * time.Second)
|
||||||
log.Println("Do something 3 seconds with error and restart")
|
log.Println("Do something 3 seconds with error and restart")
|
||||||
return errors.New("Error #1!")
|
return errors.New("Error #1!")
|
||||||
}, rutina.RestartIfFail)
|
}, rutina.RestartIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
<-time.After(4 * time.Second)
|
<-time.After(4 * time.Second)
|
||||||
log.Println("Do something 4 seconds with error and do nothing")
|
log.Println("Do something 4 seconds with error and do nothing")
|
||||||
return errors.New("Error #2!")
|
return errors.New("Error #2!")
|
||||||
}, rutina.DoNothingIfFail)
|
}, rutina.DoNothingIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
<-time.After(10 * time.Second)
|
<-time.After(10 * time.Second)
|
||||||
log.Println("Do something 10 seconds with error and close context")
|
log.Println("Do something 10 seconds with error and close context")
|
||||||
return errors.New("Successfully shutdown at proper place")
|
return errors.New("Successfully shutdown at proper place")
|
||||||
}, rutina.ShutdownIfFail)
|
}, rutina.ShutdownIfError)
|
||||||
|
|
||||||
r.Go(func(ctx context.Context) error {
|
r.Go(func(ctx context.Context) error {
|
||||||
for {
|
for {
|
||||||
|
|
16
mixins.go
16
mixins.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mixin interface
|
// Mixin interface
|
||||||
|
@ -72,3 +73,18 @@ func (l LifecycleMixin) apply(r *Rutina) {
|
||||||
func WithLifecycleListener(listener LifecycleListener) *LifecycleMixin {
|
func WithLifecycleListener(listener LifecycleListener) *LifecycleMixin {
|
||||||
return &LifecycleMixin{Listener: listener}
|
return &LifecycleMixin{Listener: listener}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListenOsSignalsMixin struct {
|
||||||
|
Signals []os.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l ListenOsSignalsMixin) apply(r *Rutina) {
|
||||||
|
r.autoListenSignals = l.Signals
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithListenOsSignals(signals ...os.Signal) *ListenOsSignalsMixin {
|
||||||
|
if len(signals) == 0 {
|
||||||
|
signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
|
||||||
|
}
|
||||||
|
return &ListenOsSignalsMixin{Signals: signals}
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ package rutina
|
||||||
type Options int
|
type Options int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ShutdownIfFail Options = iota // Shutdown all routines if fail
|
ShutdownIfError Options = iota // Shutdown all routines if fail
|
||||||
RestartIfFail // Restart this routine if fail
|
RestartIfError // Restart this routine if fail
|
||||||
DoNothingIfFail // Do nothing on fail
|
DoNothingIfError // Do nothing on fail
|
||||||
ShutdownIfDone // Shutdown all routines if this routine done without errors
|
ShutdownIfDone // Shutdown all routines if this routine done without errors
|
||||||
RestartIfDone // Restart if this routine done without errors
|
RestartIfDone // Restart if this routine done without errors
|
||||||
DoNothingIfDone // Do nothing if this routine done without errors
|
DoNothingIfDone // Do nothing if this routine done without errors
|
||||||
|
|
30
rutina.go
30
rutina.go
|
@ -20,8 +20,9 @@ type Rutina struct {
|
||||||
err error // First error that shutdowns all routines
|
err error // First error that shutdowns all routines
|
||||||
logger *log.Logger // Optional logger
|
logger *log.Logger // Optional logger
|
||||||
counter *uint64 // Optional counter that names routines with increment ids for debug purposes at logger
|
counter *uint64 // Optional counter that names routines with increment ids for debug purposes at logger
|
||||||
errCh chan error // Optional channel for errors when RestartIfFail and DoNothingIfFail
|
errCh chan error // Optional channel for errors when RestartIfError and DoNothingIfError
|
||||||
lifecycleListener LifecycleListener // Optional listener for events
|
lifecycleListener LifecycleListener // Optional listener for events
|
||||||
|
autoListenSignals []os.Signal // Optional listening os signals, default disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instance with builtin context
|
// New instance with builtin context
|
||||||
|
@ -37,6 +38,9 @@ func (r *Rutina) With(mixins ...Mixin) *Rutina {
|
||||||
for _, m := range mixins {
|
for _, m := range mixins {
|
||||||
m.apply(r)
|
m.apply(r)
|
||||||
}
|
}
|
||||||
|
if r.autoListenSignals != nil {
|
||||||
|
r.ListenOsSignals(r.autoListenSignals...)
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,15 +50,15 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
|
||||||
if r.ctx.Err() != nil {
|
if r.ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onFail := ShutdownIfFail
|
onFail := ShutdownIfError
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
switch o {
|
switch o {
|
||||||
case ShutdownIfFail:
|
case ShutdownIfError:
|
||||||
onFail = ShutdownIfFail
|
onFail = ShutdownIfError
|
||||||
case RestartIfFail:
|
case RestartIfError:
|
||||||
onFail = RestartIfFail
|
onFail = RestartIfError
|
||||||
case DoNothingIfFail:
|
case DoNothingIfError:
|
||||||
onFail = DoNothingIfFail
|
onFail = DoNothingIfError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDone := ShutdownIfDone
|
onDone := ShutdownIfDone
|
||||||
|
@ -75,7 +79,7 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
|
||||||
id := atomic.AddUint64(r.counter, 1)
|
id := atomic.AddUint64(r.counter, 1)
|
||||||
r.lifecycleEvent(EventRoutineStart, int(id))
|
r.lifecycleEvent(EventRoutineStart, int(id))
|
||||||
if err := doer(r.ctx); err != nil {
|
if err := doer(r.ctx); err != nil {
|
||||||
r.lifecycleEvent(EventRoutineFail, int(id))
|
r.lifecycleEvent(EventRoutineError, int(id))
|
||||||
r.lifecycleEvent(EventRoutineStop, int(id))
|
r.lifecycleEvent(EventRoutineStop, int(id))
|
||||||
// errors history
|
// errors history
|
||||||
if r.errCh != nil {
|
if r.errCh != nil {
|
||||||
|
@ -83,13 +87,13 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
|
||||||
}
|
}
|
||||||
// region routine failed
|
// region routine failed
|
||||||
switch onFail {
|
switch onFail {
|
||||||
case ShutdownIfFail:
|
case ShutdownIfError:
|
||||||
// Save error only if shutdown all routines
|
// Save error only if shutdown all routines
|
||||||
r.onceErr.Do(func() {
|
r.onceErr.Do(func() {
|
||||||
r.err = err
|
r.err = err
|
||||||
})
|
})
|
||||||
r.Cancel()
|
r.Cancel()
|
||||||
case RestartIfFail:
|
case RestartIfError:
|
||||||
r.Go(doer, opts...)
|
r.Go(doer, opts...)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -108,7 +112,7 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors returns chan for all errors, event if DoNothingIfFail or RestartIfFail set.
|
// Errors returns chan for all errors, event if DoNothingIfError or RestartIfError set.
|
||||||
// By default it nil. Use MixinErrChan to turn it on
|
// By default it nil. Use MixinErrChan to turn it on
|
||||||
func (r *Rutina) Errors() <-chan error {
|
func (r *Rutina) Errors() <-chan error {
|
||||||
return r.errCh
|
return r.errCh
|
||||||
|
@ -140,7 +144,7 @@ func (r *Rutina) Wait() error {
|
||||||
if r.err == nil {
|
if r.err == nil {
|
||||||
r.lifecycleEvent(EventAppComplete, 0)
|
r.lifecycleEvent(EventAppComplete, 0)
|
||||||
} else {
|
} else {
|
||||||
r.lifecycleEvent(EventAppFail, 0)
|
r.lifecycleEvent(EventAppError, 0)
|
||||||
}
|
}
|
||||||
if r.errCh != nil {
|
if r.errCh != nil {
|
||||||
close(r.errCh)
|
close(r.errCh)
|
||||||
|
|
Loading…
Reference in a new issue