diff --git a/README.md b/README.md index c6eceab..5b1cde6 100755 --- a/README.md +++ b/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: 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()` ## When it need? @@ -42,16 +42,16 @@ r.Go(func (ctx context.Context) error { Available options of run policy: -* `ShutdownIfFail` - Shutdown all routines if this routine fails -* `RestartIfFail` - Restart this routine if it fail -* `DoNothingIfFail` - Do nothing just stop this routine if it fail +* `ShutdownIfError` - Shutdown all routines if this routine returns error +* `RestartIfError` - Restart this routine if this routine returns error +* `DoNothingIfError` - Do nothing just stop this routine if this routine returns error * `ShutdownIfDone` - Shutdown all routines if this routine done without errors * `RestartIfDone` - Restart if this routine done without errors * `DoNothingIfDone` - Do nothing if this routine done without errors Default policy: -`ShutdownIfFail` && `ShutdownIfDone` +`ShutdownIfError` && `ShutdownIfDone` (just like [errgroup](https://godoc.org/golang.org/x/sync/errgroup)) @@ -61,17 +61,17 @@ Default policy: r.Go(func(ctx context.Context) error { // If this routine produce no error - it just restarts // 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 { // If this routine produce no error - it just completes // 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 { // If this routine produce no error - all other routines will shutdown (because context cancels) // If it returns error - it will be restarted -}, rutina.RestartIfFail) +}, rutina.RestartIfError) r.Go(func(ctx context.Context) error { // 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 * `EventRoutineStop` - Fires when routine stopped with any result * `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 * `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 @@ -151,6 +151,22 @@ err := <- r.Errors() 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 HTTP server with graceful shutdown [`example/http_server.go`](https://github.com/NeonXP/rutina/blob/master/example/http_server.go) diff --git a/event_string.go b/event_string.go index 05f1b7a..6501cad 100644 --- a/event_string.go +++ b/event_string.go @@ -11,15 +11,15 @@ func _() { _ = x[EventRoutineStart-0] _ = x[EventRoutineStop-1] _ = x[EventRoutineComplete-2] - _ = x[EventRoutineFail-3] + _ = x[EventRoutineError-3] _ = x[EventAppStop-4] _ = 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 { if i < 0 || i >= Event(len(_Event_index)-1) { diff --git a/events.go b/events.go index d435e62..6159343 100644 --- a/events.go +++ b/events.go @@ -8,10 +8,10 @@ const ( EventRoutineStart Event = iota EventRoutineStop EventRoutineComplete - EventRoutineFail + EventRoutineError EventAppStop EventAppComplete - EventAppFail + EventAppError ) // Hook is function that calls when event fired diff --git a/example/policies.go b/example/policies.go index 3e14f5a..6addea7 100644 --- a/example/policies.go +++ b/example/policies.go @@ -21,31 +21,31 @@ func main() { <-time.After(1 * time.Second) log.Println("Do something 1 second without errors and restart") return nil - }, rutina.RestartIfDone, rutina.ShutdownIfFail) + }, rutina.RestartIfDone, rutina.ShutdownIfError) r.Go(func(ctx context.Context) error { <-time.After(2 * time.Second) log.Println("Do something 2 seconds without errors and do nothing") return nil - }, rutina.DoNothingIfDone, rutina.ShutdownIfFail) + }, rutina.DoNothingIfDone, rutina.ShutdownIfError) r.Go(func(ctx context.Context) error { <-time.After(3 * time.Second) log.Println("Do something 3 seconds with error and restart") return errors.New("Error #1!") - }, rutina.RestartIfFail) + }, rutina.RestartIfError) r.Go(func(ctx context.Context) error { <-time.After(4 * time.Second) log.Println("Do something 4 seconds with error and do nothing") return errors.New("Error #2!") - }, rutina.DoNothingIfFail) + }, rutina.DoNothingIfError) r.Go(func(ctx context.Context) error { <-time.After(10 * time.Second) log.Println("Do something 10 seconds with error and close context") return errors.New("Successfully shutdown at proper place") - }, rutina.ShutdownIfFail) + }, rutina.ShutdownIfError) r.Go(func(ctx context.Context) error { for { diff --git a/mixins.go b/mixins.go index 1344d07..aea08ef 100755 --- a/mixins.go +++ b/mixins.go @@ -4,6 +4,7 @@ import ( "context" "log" "os" + "syscall" ) // Mixin interface @@ -72,3 +73,18 @@ func (l LifecycleMixin) apply(r *Rutina) { func WithLifecycleListener(listener LifecycleListener) *LifecycleMixin { 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} +} diff --git a/options.go b/options.go index 16c72e8..be91068 100644 --- a/options.go +++ b/options.go @@ -4,10 +4,10 @@ package rutina type Options int const ( - ShutdownIfFail Options = iota // Shutdown all routines if fail - RestartIfFail // Restart this routine if fail - DoNothingIfFail // Do nothing on fail - ShutdownIfDone // Shutdown all routines if this routine done without errors - RestartIfDone // Restart if this routine done without errors - DoNothingIfDone // Do nothing if this routine done without errors + ShutdownIfError Options = iota // Shutdown all routines if fail + RestartIfError // Restart this routine if fail + DoNothingIfError // Do nothing on fail + ShutdownIfDone // Shutdown all routines if this routine done without errors + RestartIfDone // Restart if this routine done without errors + DoNothingIfDone // Do nothing if this routine done without errors ) diff --git a/rutina.go b/rutina.go index dccce9e..34d7723 100755 --- a/rutina.go +++ b/rutina.go @@ -20,8 +20,9 @@ type Rutina struct { err error // First error that shutdowns all routines logger *log.Logger // Optional 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 + autoListenSignals []os.Signal // Optional listening os signals, default disabled } // New instance with builtin context @@ -37,6 +38,9 @@ func (r *Rutina) With(mixins ...Mixin) *Rutina { for _, m := range mixins { m.apply(r) } + if r.autoListenSignals != nil { + r.ListenOsSignals(r.autoListenSignals...) + } return r } @@ -46,15 +50,15 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) { if r.ctx.Err() != nil { return } - onFail := ShutdownIfFail + onFail := ShutdownIfError for _, o := range opts { switch o { - case ShutdownIfFail: - onFail = ShutdownIfFail - case RestartIfFail: - onFail = RestartIfFail - case DoNothingIfFail: - onFail = DoNothingIfFail + case ShutdownIfError: + onFail = ShutdownIfError + case RestartIfError: + onFail = RestartIfError + case DoNothingIfError: + onFail = DoNothingIfError } } onDone := ShutdownIfDone @@ -75,7 +79,7 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) { id := atomic.AddUint64(r.counter, 1) r.lifecycleEvent(EventRoutineStart, int(id)) if err := doer(r.ctx); err != nil { - r.lifecycleEvent(EventRoutineFail, int(id)) + r.lifecycleEvent(EventRoutineError, int(id)) r.lifecycleEvent(EventRoutineStop, int(id)) // errors history if r.errCh != nil { @@ -83,13 +87,13 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) { } // region routine failed switch onFail { - case ShutdownIfFail: + case ShutdownIfError: // Save error only if shutdown all routines r.onceErr.Do(func() { r.err = err }) r.Cancel() - case RestartIfFail: + case RestartIfError: r.Go(doer, opts...) } // 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 func (r *Rutina) Errors() <-chan error { return r.errCh @@ -140,7 +144,7 @@ func (r *Rutina) Wait() error { if r.err == nil { r.lifecycleEvent(EventAppComplete, 0) } else { - r.lifecycleEvent(EventAppFail, 0) + r.lifecycleEvent(EventAppError, 0) } if r.errCh != nil { close(r.errCh)