diff --git a/mrun/hook.go b/mrun/hook.go index e1a6ee6..3bab3f8 100644 --- a/mrun/hook.go +++ b/mrun/hook.go @@ -3,158 +3,125 @@ package mrun import ( "context" - "github.com/mediocregopher/mediocre-go-lib/mctx" + "github.com/mediocregopher/mediocre-go-lib/mcmp" ) // Hook describes a function which can be registered to trigger on an event via // the WithHook function. type Hook func(context.Context) error -type ctxKey int - -const ( - ctxKeyHookEls ctxKey = iota - ctxKeyNumHooks -) - -type ctxKeyWrap struct { - key ctxKey - userKey interface{} +type hookKey struct { + key interface{} } -// because we want Hooks to be called in the order created, taking into account -// the creation of children and their hooks as well, we create a sequence of -// elements which can either be a Hook or a child. -type hookEl struct { - hook Hook - child context.Context -} - -func ctxKeys(userKey interface{}) (ctxKeyWrap, ctxKeyWrap) { - return ctxKeyWrap{ - key: ctxKeyHookEls, - userKey: userKey, - }, ctxKeyWrap{ - key: ctxKeyNumHooks, - userKey: userKey, - } -} - -// getHookEls retrieves a copy of the []hookEl in the Context and possibly -// appends more elements if more children have been added since that []hookEl -// was created. -// -// this also returns the latest numHooks value for convenience. -func getHookEls(ctx context.Context, userKey interface{}) ([]hookEl, int) { - hookElsKey, numHooksKey := ctxKeys(userKey) - lastNumHooks, _ := mctx.LocalValue(ctx, numHooksKey).(int) - lastHookEls, _ := mctx.LocalValue(ctx, hookElsKey).([]hookEl) - children := mctx.Children(ctx) - - // plus 1 in case we wanna append something else outside this function - hookEls := make([]hookEl, len(lastHookEls), lastNumHooks+len(children)+1) - copy(hookEls, lastHookEls) - - lastNumChildren := len(lastHookEls) - lastNumHooks - for _, child := range children[lastNumChildren:] { - hookEls = append(hookEls, hookEl{child: child}) - } - return hookEls, lastNumHooks -} - -// WithHook registers a Hook under a typed key. The Hook will be called when +// AddHook registers a Hook under a typed key. The Hook will be called when // TriggerHooks is called with that same key. Multiple Hooks can be registered // for the same key, and will be called sequentially when triggered. // // Hooks will be called with whatever Context is passed into TriggerHooks. -func WithHook(ctx context.Context, key interface{}, hook Hook) context.Context { - hookEls, numHooks := getHookEls(ctx, key) - hookEls = append(hookEls, hookEl{hook: hook}) - - hookElsKey, numHooksKey := ctxKeys(key) - ctx = mctx.WithLocalValue(ctx, hookElsKey, hookEls) - ctx = mctx.WithLocalValue(ctx, numHooksKey, numHooks+1) - return ctx +func AddHook(cmp *mcmp.Component, key interface{}, hook Hook) { + mcmp.AddSeriesValue(cmp, hookKey{key}, hook) } -func triggerHooks(ctx context.Context, userKey interface{}, next func([]hookEl) (hookEl, []hookEl)) error { - hookEls, _ := getHookEls(ctx, userKey) - var hookEl hookEl +func triggerHooks( + ctx context.Context, + cmp *mcmp.Component, + key interface{}, + next func([]mcmp.SeriesElement) (mcmp.SeriesElement, []mcmp.SeriesElement), +) error { + els := mcmp.GetSeriesElements(cmp, hookKey{key}) + var el mcmp.SeriesElement for { - if len(hookEls) == 0 { + if len(els) == 0 { break } - hookEl, hookEls = next(hookEls) - if hookEl.child != nil { - if err := triggerHooks(hookEl.child, userKey, next); err != nil { + el, els = next(els) + if el.Child != nil { + if err := triggerHooks(ctx, el.Child, key, next); err != nil { return err } - } else if err := hookEl.hook(ctx); err != nil { + continue + } + hook := el.Value.(Hook) + if err := hook(ctx); err != nil { return err } } return nil } -// TriggerHooks causes all Hooks registered with WithHook on the Context (and -// its predecessors) under the given key to be called in the order they were -// registered. +// TriggerHooks causes all Hooks registered with AddHook on the Component under +// the given key to be called in the order they were registered. The given +// Context is passed into all Hooks being called. // // If any Hook returns an error no further Hooks will be called and that error // will be returned. // -// If the Context has children (see the mctx package), and those children have +// If the Component has children (see the mcmp package), and those children have // Hooks registered under this key, then their Hooks will be called in the // expected order. See package docs for an example. -func TriggerHooks(ctx context.Context, key interface{}) error { - return triggerHooks(ctx, key, func(hookEls []hookEl) (hookEl, []hookEl) { - return hookEls[0], hookEls[1:] - }) +func TriggerHooks( + ctx context.Context, + cmp *mcmp.Component, + key interface{}, +) error { + next := func(els []mcmp.SeriesElement) ( + mcmp.SeriesElement, []mcmp.SeriesElement, + ) { + return els[0], els[1:] + } + return triggerHooks(ctx, cmp, key, next) } // TriggerHooksReverse is the same as TriggerHooks except that registered Hooks // are called in the reverse order in which they were registered. -func TriggerHooksReverse(ctx context.Context, key interface{}) error { - return triggerHooks(ctx, key, func(hookEls []hookEl) (hookEl, []hookEl) { - last := len(hookEls) - 1 - return hookEls[last], hookEls[:last] - }) +func TriggerHooksReverse(ctx context.Context, cmp *mcmp.Component, key interface{}) error { + next := func(els []mcmp.SeriesElement) ( + mcmp.SeriesElement, []mcmp.SeriesElement, + ) { + last := len(els) - 1 + return els[last], els[:last] + } + return triggerHooks(ctx, cmp, key, next) } type builtinEvent int const ( - start builtinEvent = iota - stop + initEvent builtinEvent = iota + shutdownEvent ) -// WithStartHook registers the given Hook to run when Start is called. This is a -// special case of WithHook. +// InitHook registers the given Hook to run when Init is called. This is a +// special case of AddHook. // -// As a convention Hooks running on the start event should block only as long as +// As a convention Hooks running on the init event should block only as long as // it takes to ensure that whatever is running can do so successfully. For // short-lived tasks this isn't a problem, but long-lived tasks (e.g. a web // server) will want to use the Hook only to initialize, and spawn off a // go-routine to do their actual work. Long-lived tasks should set themselves up -// to stop on the stop event (see WithStopHook). -func WithStartHook(ctx context.Context, hook Hook) context.Context { - return WithHook(ctx, start, hook) +// to shutdown on the shutdown event (see ShutdownHook). +func InitHook(cmp *mcmp.Component, hook Hook) { + AddHook(cmp, initEvent, hook) } -// Start runs all Hooks registered using WithStartHook. This is a special case -// of TriggerHooks. -func Start(ctx context.Context) error { - return TriggerHooks(ctx, start) +// Init runs all Hooks registered using InitHook. This is a special case of +// TriggerHooks. +func Init(ctx context.Context, cmp *mcmp.Component) error { + return TriggerHooks(ctx, cmp, initEvent) } -// WithStopHook registers the given Hook to run when Stop is called. This is a -// special case of WithHook. -func WithStopHook(ctx context.Context, hook Hook) context.Context { - return WithHook(ctx, stop, hook) +// ShutdownHook registers the given Hook to run when Shutdown is called. This is +// a special case of AddHook. +// +// See InitHook for more on the relationship between Init(Hook) and +// Shutdown(Hook). +func ShutdownHook(cmp *mcmp.Component, hook Hook) { + AddHook(cmp, shutdownEvent, hook) } -// Stop runs all Hooks registered using WithStopHook in the reverse order in +// Shutdown runs all Hooks registered using ShutdownHook in the reverse order in // which they were registered. This is a special case of TriggerHooks. -func Stop(ctx context.Context) error { - return TriggerHooksReverse(ctx, stop) +func Shutdown(ctx context.Context, cmp *mcmp.Component) error { + return TriggerHooksReverse(ctx, cmp, shutdownEvent) } diff --git a/mrun/hook_test.go b/mrun/hook_test.go index d660fe5..8dfc77f 100644 --- a/mrun/hook_test.go +++ b/mrun/hook_test.go @@ -4,7 +4,7 @@ import ( "context" . "testing" - "github.com/mediocregopher/mediocre-go-lib/mctx" + "github.com/mediocregopher/mediocre-go-lib/mcmp" "github.com/mediocregopher/mediocre-go-lib/mtest/massert" ) @@ -17,34 +17,31 @@ func TestHooks(t *T) { } } - ctx := context.Background() - ctx = WithHook(ctx, 0, mkHook(1)) - ctx = WithHook(ctx, 0, mkHook(2)) + cmp := new(mcmp.Component) + AddHook(cmp, 0, mkHook(1)) + AddHook(cmp, 0, mkHook(2)) - ctxA := mctx.NewChild(ctx, "a") - ctxA = WithHook(ctxA, 0, mkHook(3)) - ctxA = WithHook(ctxA, 999, mkHook(999)) // different key - ctx = mctx.WithChild(ctx, ctxA) + cmpA := cmp.Child("a") + AddHook(cmpA, 0, mkHook(3)) + AddHook(cmpA, 999, mkHook(999)) // different key - ctx = WithHook(ctx, 0, mkHook(4)) + AddHook(cmp, 0, mkHook(4)) - ctxB := mctx.NewChild(ctx, "b") - ctxB = WithHook(ctxB, 0, mkHook(5)) - ctxB1 := mctx.NewChild(ctxB, "1") - ctxB1 = WithHook(ctxB1, 0, mkHook(6)) - ctxB = mctx.WithChild(ctxB, ctxB1) - ctx = mctx.WithChild(ctx, ctxB) + cmpB := cmp.Child("b") + AddHook(cmpB, 0, mkHook(5)) + cmpB1 := cmpB.Child("1") + AddHook(cmpB1, 0, mkHook(6)) - ctx = WithHook(ctx, 0, mkHook(7)) + AddHook(cmp, 0, mkHook(7)) massert.Require(t, - massert.Nil(TriggerHooks(ctx, 0)), + massert.Nil(TriggerHooks(context.Background(), cmp, 0)), massert.Equal([]int{1, 2, 3, 4, 5, 6, 7}, out), ) out = nil massert.Require(t, - massert.Nil(TriggerHooksReverse(ctx, 0)), + massert.Nil(TriggerHooksReverse(context.Background(), cmp, 0)), massert.Equal([]int{7, 6, 5, 4, 3, 2, 1}, out), ) } diff --git a/mrun/mrun.go b/mrun/mrun.go index 1564b1c..10183c5 100644 --- a/mrun/mrun.go +++ b/mrun/mrun.go @@ -1,12 +1,12 @@ -// Package mrun provides the ability to register callback hooks on contexts, as -// well as some convenience functions which allow using a context as a +// Package mrun provides the ability to register callback hooks on Components, +// as well as some convenience functions which allow using a context as a // wait-group. // // Hooks // -// Hooks are registered onto contexts and later called in bulk. mrun will take +// Hooks are registered onto Components and later called in bulk. mrun will take // into account the order Hooks are registered, including Hooks within a -// context's children (see mctx package), and execute them in the same order +// Component's children (see mcmp package), and execute them in the same order // they were registered. For example: // // newHook := func(i int) mrun.Hook { @@ -16,15 +16,14 @@ // } // } // -// ctx := context.Background() -// ctx = mrun.WithStartHook(ctx, newHook(0)) +// cmp := new(mcmp.Component) +// mrun.InitHook(cmp, newHook(0)) // -// child := mctx.NewChild(ctx, "child") -// child = mrun.WithStartHook(child, newHook(1)) -// ctx = mctx.WithChild(ctx, child) +// cmpChild := cmp.Child("child") +// mrun.InitHook(cmpChild, newHook(1)) // -// ctx = mrun.WithStartHook(ctx, newHook(2)) -// mrun.Start(ctx) // prints "0", "1", then "2" +// mrun.InitHook(cmp, newHook(2)) +// mrun.Init(context.Background(), cmp) // prints "0", "1", then "2" // package mrun