mrun: rename functions and add another trigger function which will run hooks in reverse, for use in OnStop

This commit is contained in:
Brian Picciano 2019-01-12 19:55:41 -05:00
parent 8a8cebd127
commit ee77656f39
2 changed files with 64 additions and 43 deletions

View File

@ -1,5 +1,5 @@
// Package mrun extends mctx to include event hooks and tracking of the liveness // Package mrun extends mctx to include runtime event hooks and tracking of the
// of spawned go-routines. // liveness of spawned go-routines.
package mrun package mrun
import ( import (
@ -101,21 +101,22 @@ type ctxEventKeyWrap struct {
} }
// Hook describes a function which can be registered to trigger on an event via // Hook describes a function which can be registered to trigger on an event via
// the OnEvent function. // the RegisterHook function.
type Hook func(mctx.Context) error type Hook func(mctx.Context) error
// OnEvent registers a Hook under a typed key. The Hook will be called when // RegisterHook registers a Hook under a typed key. The Hook will be called when
// TriggerEvent is called with that same key. Multiple Hooks can be registered // TriggerHooks is called with that same key. Multiple Hooks can be registered
// for the same key, and will be called sequentially when triggered. // for the same key, and will be called sequentially when triggered.
// //
// OnEvent registers Hooks onto the root of the given Context. Therefore, Hooks // RegisterHook registers Hooks onto the root of the given Context. Therefore,
// will be triggered in the global order they were registered (i.e. if a Hook is // Hooks will be triggered in the global order they were registered. For
// registered on a Context, then one registered on a child of that Context, then // example: if one Hook is registered on a Context, then one is registered on a
// another on the original Context again, the three Hooks will be triggered in // child of that Context, then another one is registered on the original Context
// the order: parent, child, parent). // again, the three Hooks will be triggered in the order: parent, child,
// parent.
// //
// Hooks will be called with whatever Context is passed into TriggerEvent. // Hooks will be called with whatever Context is passed into TriggerHooks.
func OnEvent(ctx mctx.Context, key interface{}, hook Hook) { func RegisterHook(ctx mctx.Context, key interface{}, hook Hook) {
ctx = mctx.Root(ctx) ctx = mctx.Root(ctx)
mctx.GetSetMutableValue(ctx, false, ctxEventKeyWrap{key}, func(v interface{}) interface{} { mctx.GetSetMutableValue(ctx, false, ctxEventKeyWrap{key}, func(v interface{}) interface{} {
hooks, _ := v.([]Hook) hooks, _ := v.([]Hook)
@ -123,23 +124,17 @@ func OnEvent(ctx mctx.Context, key interface{}, hook Hook) {
}) })
} }
// TriggerEvent causes all Hooks registered with OnEvent under the given key to func triggerHooks(ctx mctx.Context, key interface{}, next func([]Hook) (Hook, []Hook)) error {
// be called sequentially, using the given Context as their input. The given
// Context does not need to be the root Context (see OnEvent).
//
// If any Hook returns an error no further Hooks will be called and that error
// will be returned.
//
// TriggerEvent causes all Hooks which were called to be de-registered. If an
// error caused execution to stop prematurely then any Hooks which were not
// called will remain registered.
func TriggerEvent(ctx mctx.Context, key interface{}) error {
rootCtx := mctx.Root(ctx) rootCtx := mctx.Root(ctx)
var err error var err error
mctx.GetSetMutableValue(rootCtx, false, ctxEventKeyWrap{key}, func(i interface{}) interface{} { mctx.GetSetMutableValue(rootCtx, false, ctxEventKeyWrap{key}, func(i interface{}) interface{} {
var hook Hook
hooks, _ := i.([]Hook) hooks, _ := i.([]Hook)
for _, hook := range hooks { for {
hooks = hooks[1:] if len(hooks) == 0 {
break
}
hook, hooks = next(hooks)
// err here is the var outside GetSetMutableValue, we lift it out // err here is the var outside GetSetMutableValue, we lift it out
if err = hook(ctx); err != nil { if err = hook(ctx); err != nil {
@ -158,6 +153,32 @@ func TriggerEvent(ctx mctx.Context, key interface{}) error {
return err return err
} }
// TriggerHooks causes all Hooks registered with RegisterHook under the given
// key to be called in the global order they were registered, using the given
// Context as their input parameter. The given Context does not need to be the
// root Context (see RegisterHook).
//
// If any Hook returns an error no further Hooks will be called and that error
// will be returned.
//
// TriggerHooks causes all Hooks which were called to be de-registered. If an
// error caused execution to stop prematurely then any Hooks which were not
// called will remain registered.
func TriggerHooks(ctx mctx.Context, key interface{}) error {
return triggerHooks(ctx, key, func(hooks []Hook) (Hook, []Hook) {
return hooks[0], hooks[1:]
})
}
// TriggerHooksReverse is the same as TriggerHooks except that registered Hooks
// are called in the reverse order in which they were registered.
func TriggerHooksReverse(ctx mctx.Context, key interface{}) error {
return triggerHooks(ctx, key, func(hooks []Hook) (Hook, []Hook) {
last := len(hooks) - 1
return hooks[last], hooks[:last]
})
}
type builtinEvent int type builtinEvent int
const ( const (
@ -166,7 +187,7 @@ const (
) )
// OnStart registers the given Hook to run when Start is called. This is a // OnStart registers the given Hook to run when Start is called. This is a
// special case of OnEvent. // special case of RegisterHook.
// //
// As a convention Hooks running on the start event should block only as long as // As a convention Hooks running on the start event should block only as long as
// it takes to ensure that whatever is running can do so successfully. For // it takes to ensure that whatever is running can do so successfully. For
@ -175,23 +196,23 @@ const (
// go-routine to do their actual work. Long-lived tasks should set themselves up // go-routine to do their actual work. Long-lived tasks should set themselves up
// to stop on the stop event (see OnStop). // to stop on the stop event (see OnStop).
func OnStart(ctx mctx.Context, hook Hook) { func OnStart(ctx mctx.Context, hook Hook) {
OnEvent(ctx, start, hook) RegisterHook(ctx, start, hook)
} }
// Start runs all Hooks registered using OnStart. This is a special case of // Start runs all Hooks registered using OnStart. This is a special case of
// TriggerEvent. // TriggerHooks.
func Start(ctx mctx.Context) error { func Start(ctx mctx.Context) error {
return TriggerEvent(ctx, start) return TriggerHooks(ctx, start)
} }
// OnStop registers the given Hook to run when Stop is called. This is a special // OnStop registers the given Hook to run when Stop is called. This is a special
// case of OnEvent. // case of RegisterHook.
func OnStop(ctx mctx.Context, hook Hook) { func OnStop(ctx mctx.Context, hook Hook) {
OnEvent(ctx, stop, hook) RegisterHook(ctx, stop, hook)
} }
// Stop runs all Hooks registered using OnStop. This is a special case of // Stop runs all Hooks registered using OnStop in the reverse order in which
// TriggerEvent. // they were registered. This is a special case of TriggerHooks.
func Stop(ctx mctx.Context) error { func Stop(ctx mctx.Context) error {
return TriggerEvent(ctx, stop) return TriggerHooksReverse(ctx, stop)
} }

View File

@ -142,7 +142,7 @@ func TestThreadWait(t *T) {
}) })
} }
func TestEvent(t *T) { func TestHooks(t *T) {
ch := make(chan int, 10) ch := make(chan int, 10)
ctx := mctx.New() ctx := mctx.New()
ctxChild := mctx.ChildOf(ctx, "child") ctxChild := mctx.ChildOf(ctx, "child")
@ -154,18 +154,18 @@ func TestEvent(t *T) {
} }
} }
OnEvent(ctx, 0, mkHook(0)) RegisterHook(ctx, 0, mkHook(0))
OnEvent(ctxChild, 0, mkHook(1)) RegisterHook(ctxChild, 0, mkHook(1))
OnEvent(ctx, 0, mkHook(2)) RegisterHook(ctx, 0, mkHook(2))
bogusErr := errors.New("bogus error") bogusErr := errors.New("bogus error")
OnEvent(ctxChild, 0, func(mctx.Context) error { return bogusErr }) RegisterHook(ctxChild, 0, func(mctx.Context) error { return bogusErr })
OnEvent(ctx, 0, mkHook(3)) RegisterHook(ctx, 0, mkHook(3))
OnEvent(ctx, 0, mkHook(4)) RegisterHook(ctx, 0, mkHook(4))
massert.Fatal(t, massert.All( massert.Fatal(t, massert.All(
massert.Equal(bogusErr, TriggerEvent(ctx, 0)), massert.Equal(bogusErr, TriggerHooks(ctx, 0)),
massert.Equal(0, <-ch), massert.Equal(0, <-ch),
massert.Equal(1, <-ch), massert.Equal(1, <-ch),
massert.Equal(2, <-ch), massert.Equal(2, <-ch),
@ -181,7 +181,7 @@ func TestEvent(t *T) {
} }
massert.Fatal(t, massert.All( massert.Fatal(t, massert.All(
massert.Nil(TriggerEvent(ctx, 0)), massert.Nil(TriggerHooks(ctx, 0)),
massert.Equal(3, <-ch), massert.Equal(3, <-ch),
massert.Equal(4, <-ch), massert.Equal(4, <-ch),
)) ))