From 448008cb7c4974d06be9c26e575b8567ba347021 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 12 Jan 2019 20:14:02 -0500 Subject: [PATCH] mrun: move Hook code into hook.go --- mrun/hook.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++ mrun/hook_test.go | 54 ++++++++++++++++++++ mrun/mrun.go | 121 -------------------------------------------- mrun/mrun_test.go | 46 ----------------- 4 files changed, 178 insertions(+), 167 deletions(-) create mode 100644 mrun/hook.go create mode 100644 mrun/hook_test.go diff --git a/mrun/hook.go b/mrun/hook.go new file mode 100644 index 0000000..5991c7c --- /dev/null +++ b/mrun/hook.go @@ -0,0 +1,124 @@ +package mrun + +import "github.com/mediocregopher/mediocre-go-lib/mctx" + +type ctxEventKeyWrap struct { + key interface{} +} + +// Hook describes a function which can be registered to trigger on an event via +// the RegisterHook function. +type Hook func(mctx.Context) error + +// RegisterHook 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. +// +// RegisterHook registers Hooks onto the root of the given Context. Therefore, +// Hooks will be triggered in the global order they were registered. For +// example: if one Hook is registered on a Context, then one is registered on a +// child of that Context, then another one is registered on the original Context +// again, the three Hooks will be triggered in the order: parent, child, +// parent. +// +// Hooks will be called with whatever Context is passed into TriggerHooks. +func RegisterHook(ctx mctx.Context, key interface{}, hook Hook) { + ctx = mctx.Root(ctx) + mctx.GetSetMutableValue(ctx, false, ctxEventKeyWrap{key}, func(v interface{}) interface{} { + hooks, _ := v.([]Hook) + return append(hooks, hook) + }) +} + +func triggerHooks(ctx mctx.Context, key interface{}, next func([]Hook) (Hook, []Hook)) error { + rootCtx := mctx.Root(ctx) + var err error + mctx.GetSetMutableValue(rootCtx, false, ctxEventKeyWrap{key}, func(i interface{}) interface{} { + var hook Hook + hooks, _ := i.([]Hook) + for { + if len(hooks) == 0 { + break + } + hook, hooks = next(hooks) + + // err here is the var outside GetSetMutableValue, we lift it out + if err = hook(ctx); err != nil { + break + } + } + + // if there was an error then we want to keep all the hooks which + // weren't called. If there wasn't we want to reset the value to nil so + // the slice doesn't grow unbounded. + if err != nil { + return hooks + } + return nil + }) + 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 + +const ( + start builtinEvent = iota + stop +) + +// OnStart registers the given Hook to run when Start is called. This is a +// special case of RegisterHook. +// +// 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 +// 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 OnStop). +func OnStart(ctx mctx.Context, hook Hook) { + RegisterHook(ctx, start, hook) +} + +// Start runs all Hooks registered using OnStart. This is a special case of +// TriggerHooks. +func Start(ctx mctx.Context) error { + return TriggerHooks(ctx, start) +} + +// OnStop registers the given Hook to run when Stop is called. This is a special +// case of RegisterHook. +func OnStop(ctx mctx.Context, hook Hook) { + RegisterHook(ctx, stop, hook) +} + +// Stop runs all Hooks registered using OnStop in the reverse order in which +// they were registered. This is a special case of TriggerHooks. +func Stop(ctx mctx.Context) error { + return TriggerHooksReverse(ctx, stop) +} diff --git a/mrun/hook_test.go b/mrun/hook_test.go new file mode 100644 index 0000000..ca01a09 --- /dev/null +++ b/mrun/hook_test.go @@ -0,0 +1,54 @@ +package mrun + +import ( + "errors" + . "testing" + + "github.com/mediocregopher/mediocre-go-lib/mctx" + "github.com/mediocregopher/mediocre-go-lib/mtest/massert" +) + +func TestHooks(t *T) { + ch := make(chan int, 10) + ctx := mctx.New() + ctxChild := mctx.ChildOf(ctx, "child") + + mkHook := func(i int) Hook { + return func(mctx.Context) error { + ch <- i + return nil + } + } + + RegisterHook(ctx, 0, mkHook(0)) + RegisterHook(ctxChild, 0, mkHook(1)) + RegisterHook(ctx, 0, mkHook(2)) + + bogusErr := errors.New("bogus error") + RegisterHook(ctxChild, 0, func(mctx.Context) error { return bogusErr }) + + RegisterHook(ctx, 0, mkHook(3)) + RegisterHook(ctx, 0, mkHook(4)) + + massert.Fatal(t, massert.All( + massert.Equal(bogusErr, TriggerHooks(ctx, 0)), + massert.Equal(0, <-ch), + massert.Equal(1, <-ch), + massert.Equal(2, <-ch), + )) + + // after the error the 3 and 4 Hooks should still be registered, but not + // called yet. + + select { + case <-ch: + t.Fatal("Hooks should not have been called yet") + default: + } + + massert.Fatal(t, massert.All( + massert.Nil(TriggerHooks(ctx, 0)), + massert.Equal(3, <-ch), + massert.Equal(4, <-ch), + )) +} diff --git a/mrun/mrun.go b/mrun/mrun.go index a99f39c..2d2b84c 100644 --- a/mrun/mrun.go +++ b/mrun/mrun.go @@ -95,124 +95,3 @@ func Wait(ctx mctx.Context, cancelCh <-chan struct{}) error { return nil } - -type ctxEventKeyWrap struct { - key interface{} -} - -// Hook describes a function which can be registered to trigger on an event via -// the RegisterHook function. -type Hook func(mctx.Context) error - -// RegisterHook 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. -// -// RegisterHook registers Hooks onto the root of the given Context. Therefore, -// Hooks will be triggered in the global order they were registered. For -// example: if one Hook is registered on a Context, then one is registered on a -// child of that Context, then another one is registered on the original Context -// again, the three Hooks will be triggered in the order: parent, child, -// parent. -// -// Hooks will be called with whatever Context is passed into TriggerHooks. -func RegisterHook(ctx mctx.Context, key interface{}, hook Hook) { - ctx = mctx.Root(ctx) - mctx.GetSetMutableValue(ctx, false, ctxEventKeyWrap{key}, func(v interface{}) interface{} { - hooks, _ := v.([]Hook) - return append(hooks, hook) - }) -} - -func triggerHooks(ctx mctx.Context, key interface{}, next func([]Hook) (Hook, []Hook)) error { - rootCtx := mctx.Root(ctx) - var err error - mctx.GetSetMutableValue(rootCtx, false, ctxEventKeyWrap{key}, func(i interface{}) interface{} { - var hook Hook - hooks, _ := i.([]Hook) - for { - if len(hooks) == 0 { - break - } - hook, hooks = next(hooks) - - // err here is the var outside GetSetMutableValue, we lift it out - if err = hook(ctx); err != nil { - break - } - } - - // if there was an error then we want to keep all the hooks which - // weren't called. If there wasn't we want to reset the value to nil so - // the slice doesn't grow unbounded. - if err != nil { - return hooks - } - return nil - }) - 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 - -const ( - start builtinEvent = iota - stop -) - -// OnStart registers the given Hook to run when Start is called. This is a -// special case of RegisterHook. -// -// 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 -// 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 OnStop). -func OnStart(ctx mctx.Context, hook Hook) { - RegisterHook(ctx, start, hook) -} - -// Start runs all Hooks registered using OnStart. This is a special case of -// TriggerHooks. -func Start(ctx mctx.Context) error { - return TriggerHooks(ctx, start) -} - -// OnStop registers the given Hook to run when Stop is called. This is a special -// case of RegisterHook. -func OnStop(ctx mctx.Context, hook Hook) { - RegisterHook(ctx, stop, hook) -} - -// Stop runs all Hooks registered using OnStop in the reverse order in which -// they were registered. This is a special case of TriggerHooks. -func Stop(ctx mctx.Context) error { - return TriggerHooksReverse(ctx, stop) -} diff --git a/mrun/mrun_test.go b/mrun/mrun_test.go index 98e5d5a..70981e3 100644 --- a/mrun/mrun_test.go +++ b/mrun/mrun_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/mediocregopher/mediocre-go-lib/mctx" - "github.com/mediocregopher/mediocre-go-lib/mtest/massert" ) func TestThreadWait(t *T) { @@ -141,48 +140,3 @@ func TestThreadWait(t *T) { }) }) } - -func TestHooks(t *T) { - ch := make(chan int, 10) - ctx := mctx.New() - ctxChild := mctx.ChildOf(ctx, "child") - - mkHook := func(i int) Hook { - return func(mctx.Context) error { - ch <- i - return nil - } - } - - RegisterHook(ctx, 0, mkHook(0)) - RegisterHook(ctxChild, 0, mkHook(1)) - RegisterHook(ctx, 0, mkHook(2)) - - bogusErr := errors.New("bogus error") - RegisterHook(ctxChild, 0, func(mctx.Context) error { return bogusErr }) - - RegisterHook(ctx, 0, mkHook(3)) - RegisterHook(ctx, 0, mkHook(4)) - - massert.Fatal(t, massert.All( - massert.Equal(bogusErr, TriggerHooks(ctx, 0)), - massert.Equal(0, <-ch), - massert.Equal(1, <-ch), - massert.Equal(2, <-ch), - )) - - // after the error the 3 and 4 Hooks should still be registered, but not - // called yet. - - select { - case <-ch: - t.Fatal("Hooks should not have been called yet") - default: - } - - massert.Fatal(t, massert.All( - massert.Nil(TriggerHooks(ctx, 0)), - massert.Equal(3, <-ch), - massert.Equal(4, <-ch), - )) -}