aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Kiryukhin <alexander@kiryukhin.su>2019-04-04 22:56:53 +0300
committerAlexander Kiryukhin <alexander@kiryukhin.su>2019-04-04 22:56:53 +0300
commitc691d422395cb72283512d8956a255db10b70b44 (patch)
treec913d176c23495a8aa82ecc98089de10074c0929
parent04db7b633980e7c39e582479ea41608907aa45b2 (diff)
v0.4.3v0.4.3
Added: - Hooks for lifecycle events Fixed: - Close errors channel - Small fixes
-rwxr-xr-xREADME.md21
-rw-r--r--event_string.go29
-rw-r--r--events.go22
-rw-r--r--example/policies.go2
-rwxr-xr-xmixins.go2
-rwxr-xr-xrutina.go76
6 files changed, 124 insertions, 28 deletions
diff --git a/README.md b/README.md
index b3c5ceb..fa1a88f 100755
--- a/README.md
+++ b/README.md
@@ -96,6 +96,27 @@ err := <- r.Errors()
Disabled by default. Use `r.With(rutina.WithErrChan())` to turn on.
+## Events and hooks
+
+Rutina has own simple lifecycle events system. You can subscribe your hooks on any of this 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
+* `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
+
+Example:
+
+```go
+r.RegisterHook(rutina.EventRoutineStart, func(ev rutina.Event, rid int) error {
+ log.Println("Started routine with ID", rid)
+ return nil
+})
+```
+
## Mixins
### Usage
diff --git a/event_string.go b/event_string.go
new file mode 100644
index 0000000..05f1b7a
--- /dev/null
+++ b/event_string.go
@@ -0,0 +1,29 @@
+// Code generated by "stringer -type=Event"; DO NOT EDIT.
+
+package rutina
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[EventRoutineStart-0]
+ _ = x[EventRoutineStop-1]
+ _ = x[EventRoutineComplete-2]
+ _ = x[EventRoutineFail-3]
+ _ = x[EventAppStop-4]
+ _ = x[EventAppComplete-5]
+ _ = x[EventAppFail-6]
+}
+
+const _Event_name = "EventRoutineStartEventRoutineStopEventRoutineCompleteEventRoutineFailEventAppStopEventAppCompleteEventAppFail"
+
+var _Event_index = [...]uint8{0, 17, 33, 53, 69, 81, 97, 109}
+
+func (i Event) String() string {
+ if i < 0 || i >= Event(len(_Event_index)-1) {
+ return "Event(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Event_name[_Event_index[i]:_Event_index[i+1]]
+}
diff --git a/events.go b/events.go
new file mode 100644
index 0000000..d435e62
--- /dev/null
+++ b/events.go
@@ -0,0 +1,22 @@
+//go:generate stringer -type=Event
+package rutina
+
+// Event represents lifecycle events
+type Event int
+
+const (
+ EventRoutineStart Event = iota
+ EventRoutineStop
+ EventRoutineComplete
+ EventRoutineFail
+ EventAppStop
+ EventAppComplete
+ EventAppFail
+)
+
+// Hook is function that calls when event fired
+// Params:
+// ev Event - fired event
+// r *Rutina - pointer to rutina
+// rid int - ID of routine if present, 0 - otherwise
+type Hook func(ev Event, r *Rutina, rid int) error
diff --git a/example/policies.go b/example/policies.go
index 7a81359..3e14f5a 100644
--- a/example/policies.go
+++ b/example/policies.go
@@ -15,7 +15,7 @@ func main() {
// New instance with builtin context
r := rutina.New()
- r = r.With(rutina.WithErrChan())
+ r = r.With(rutina.WithErrChan(), rutina.WithStdLogger())
r.Go(func(ctx context.Context) error {
<-time.After(1 * time.Second)
diff --git a/mixins.go b/mixins.go
index 1cc2c76..6688f36 100755
--- a/mixins.go
+++ b/mixins.go
@@ -39,7 +39,7 @@ func WithLogger(logger *log.Logger) *MixinLogger {
// WithStdLogger adds standard logger to rutina
func WithStdLogger() *MixinLogger {
- return &MixinLogger{Logger: log.New(os.Stdout, "rutina", log.LstdFlags)}
+ return &MixinLogger{Logger: log.New(os.Stdout, "[rutina]", log.LstdFlags)}
}
func (o MixinLogger) apply(r *Rutina) {
diff --git a/rutina.go b/rutina.go
index 9531cbb..e4ef1a7 100755
--- a/rutina.go
+++ b/rutina.go
@@ -12,21 +12,23 @@ import (
//Rutina is routine manager
type Rutina struct {
- ctx context.Context // State of application (started/stopped)
- Cancel func() // Cancel func that stops all routines
- wg sync.WaitGroup // WaitGroup that wait all routines to complete
- o sync.Once // Flag that prevents overwrite first error that shutdowns all routines
- 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
+ ctx context.Context // State of application (started/stopped)
+ Cancel func() // Cancel func that stops all routines
+ wg sync.WaitGroup // WaitGroup that wait all routines to complete
+ onceErr sync.Once // Flag that prevents overwrite first error that shutdowns all routines
+ onceWait sync.Once // Flag that prevents wait already waited rutina
+ 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
+ hooks map[Event][]Hook // Lifecycle hooks
}
// New instance with builtin context
func New(mixins ...Mixin) *Rutina {
ctx, cancel := context.WithCancel(context.Background())
var counter uint64
- r := &Rutina{ctx: ctx, Cancel: cancel, counter: &counter, errCh: nil}
+ r := &Rutina{ctx: ctx, Cancel: cancel, counter: &counter, errCh: nil, hooks: map[Event][]Hook{}}
return r.With(mixins...)
}
@@ -38,8 +40,16 @@ func (r *Rutina) With(mixins ...Mixin) *Rutina {
return r
}
+func (r *Rutina) RegisterHook(ev Event, hook Hook) {
+ r.hooks[ev] = append(r.hooks[ev], hook)
+}
+
// Go routine
func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
+ // Check that context is not canceled yet
+ if r.ctx.Err() != nil {
+ return
+ }
onFail := ShutdownIfFail
for _, o := range opts {
switch o {
@@ -66,49 +76,39 @@ func (r *Rutina) Go(doer func(ctx context.Context) error, opts ...Options) {
r.wg.Add(1)
go func() {
defer r.wg.Done()
- // Check that context is not canceled yet
- if r.ctx.Err() != nil {
- return
- }
id := atomic.AddUint64(r.counter, 1)
- r.log("starting #%d", id)
+ r.fire(EventRoutineStart, int(id))
if err := doer(r.ctx); err != nil {
+ r.fire(EventRoutineFail, int(id))
+ r.fire(EventRoutineStop, int(id))
// errors history
if r.errCh != nil {
r.errCh <- err
}
// region routine failed
- r.log("error at #%d : %v", id, err)
switch onFail {
case ShutdownIfFail:
- r.log("stopping #%d", id)
// Save error only if shutdown all routines
- r.o.Do(func() {
+ r.onceErr.Do(func() {
r.err = err
})
r.Cancel()
case RestartIfFail:
- r.log("restarting #%d", id)
r.Go(doer, opts...)
- case DoNothingIfFail:
- r.log("stopping #%d", id)
}
// endregion
} else {
+ r.fire(EventRoutineComplete, int(id))
+ r.fire(EventRoutineStop, int(id))
// region routine successfully done
switch onDone {
case ShutdownIfDone:
- r.log("stopping #%d with shutdown", id)
r.Cancel()
case RestartIfDone:
- r.log("restarting #%d", id)
r.Go(doer, opts...)
- case DoNothingIfDone:
- r.log("stopping #%d", id)
}
// endregion
}
-
}()
}
@@ -138,10 +138,34 @@ func (r *Rutina) ListenOsSignals(signals ...os.Signal) {
// Wait all routines and returns first error or nil if all routines completes without errors
func (r *Rutina) Wait() error {
- r.wg.Wait()
+ r.onceWait.Do(func() {
+ r.wg.Wait()
+ r.fire(EventAppStop, 0)
+ if r.err == nil {
+ r.fire(EventAppComplete, 0)
+ } else {
+ r.fire(EventAppFail, 0)
+ }
+ if r.errCh != nil {
+ close(r.errCh)
+ }
+ })
return r.err
}
+func (r *Rutina) fire(ev Event, rid int) {
+ r.log("Event = %s Routine ID = %d", ev.String(), rid)
+ if hooks, ok := r.hooks[ev]; ok == true {
+ for _, h := range hooks {
+ if err := h(ev, r, rid); err != nil {
+ if r.errCh != nil {
+ r.errCh <- err
+ }
+ }
+ }
+ }
+}
+
// Log if can
func (r *Rutina) log(format string, args ...interface{}) {
if r.logger != nil {