mrun: refactor hooks to use Components
This commit is contained in:
parent
dd2d601081
commit
c98f154992
167
mrun/hook.go
167
mrun/hook.go
@ -3,158 +3,125 @@ package mrun
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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
|
// Hook describes a function which can be registered to trigger on an event via
|
||||||
// the WithHook function.
|
// the WithHook function.
|
||||||
type Hook func(context.Context) error
|
type Hook func(context.Context) error
|
||||||
|
|
||||||
type ctxKey int
|
type hookKey struct {
|
||||||
|
key interface{}
|
||||||
const (
|
|
||||||
ctxKeyHookEls ctxKey = iota
|
|
||||||
ctxKeyNumHooks
|
|
||||||
)
|
|
||||||
|
|
||||||
type ctxKeyWrap struct {
|
|
||||||
key ctxKey
|
|
||||||
userKey interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// because we want Hooks to be called in the order created, taking into account
|
// AddHook registers a Hook under a typed key. The Hook will be called when
|
||||||
// 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
|
|
||||||
// TriggerHooks 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.
|
||||||
//
|
//
|
||||||
// Hooks will be called with whatever Context is passed into TriggerHooks.
|
// Hooks will be called with whatever Context is passed into TriggerHooks.
|
||||||
func WithHook(ctx context.Context, key interface{}, hook Hook) context.Context {
|
func AddHook(cmp *mcmp.Component, key interface{}, hook Hook) {
|
||||||
hookEls, numHooks := getHookEls(ctx, key)
|
mcmp.AddSeriesValue(cmp, hookKey{key}, hook)
|
||||||
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 triggerHooks(ctx context.Context, userKey interface{}, next func([]hookEl) (hookEl, []hookEl)) error {
|
func triggerHooks(
|
||||||
hookEls, _ := getHookEls(ctx, userKey)
|
ctx context.Context,
|
||||||
var hookEl hookEl
|
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 {
|
for {
|
||||||
if len(hookEls) == 0 {
|
if len(els) == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
hookEl, hookEls = next(hookEls)
|
el, els = next(els)
|
||||||
if hookEl.child != nil {
|
if el.Child != nil {
|
||||||
if err := triggerHooks(hookEl.child, userKey, next); err != nil {
|
if err := triggerHooks(ctx, el.Child, key, next); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err := hookEl.hook(ctx); err != nil {
|
continue
|
||||||
|
}
|
||||||
|
hook := el.Value.(Hook)
|
||||||
|
if err := hook(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TriggerHooks causes all Hooks registered with WithHook on the Context (and
|
// TriggerHooks causes all Hooks registered with AddHook on the Component under
|
||||||
// its predecessors) under the given key to be called in the order they were
|
// the given key to be called in the order they were registered. The given
|
||||||
// registered.
|
// Context is passed into all Hooks being called.
|
||||||
//
|
//
|
||||||
// If any Hook returns an error no further Hooks will be called and that error
|
// If any Hook returns an error no further Hooks will be called and that error
|
||||||
// will be returned.
|
// 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
|
// Hooks registered under this key, then their Hooks will be called in the
|
||||||
// expected order. See package docs for an example.
|
// expected order. See package docs for an example.
|
||||||
func TriggerHooks(ctx context.Context, key interface{}) error {
|
func TriggerHooks(
|
||||||
return triggerHooks(ctx, key, func(hookEls []hookEl) (hookEl, []hookEl) {
|
ctx context.Context,
|
||||||
return hookEls[0], hookEls[1:]
|
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
|
// TriggerHooksReverse is the same as TriggerHooks except that registered Hooks
|
||||||
// are called in the reverse order in which they were registered.
|
// are called in the reverse order in which they were registered.
|
||||||
func TriggerHooksReverse(ctx context.Context, key interface{}) error {
|
func TriggerHooksReverse(ctx context.Context, cmp *mcmp.Component, key interface{}) error {
|
||||||
return triggerHooks(ctx, key, func(hookEls []hookEl) (hookEl, []hookEl) {
|
next := func(els []mcmp.SeriesElement) (
|
||||||
last := len(hookEls) - 1
|
mcmp.SeriesElement, []mcmp.SeriesElement,
|
||||||
return hookEls[last], hookEls[:last]
|
) {
|
||||||
})
|
last := len(els) - 1
|
||||||
|
return els[last], els[:last]
|
||||||
|
}
|
||||||
|
return triggerHooks(ctx, cmp, key, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
type builtinEvent int
|
type builtinEvent int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
start builtinEvent = iota
|
initEvent builtinEvent = iota
|
||||||
stop
|
shutdownEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithStartHook registers the given Hook to run when Start is called. This is a
|
// InitHook registers the given Hook to run when Init is called. This is a
|
||||||
// special case of WithHook.
|
// 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
|
// 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
|
// 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
|
// 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
|
// go-routine to do their actual work. Long-lived tasks should set themselves up
|
||||||
// to stop on the stop event (see WithStopHook).
|
// to shutdown on the shutdown event (see ShutdownHook).
|
||||||
func WithStartHook(ctx context.Context, hook Hook) context.Context {
|
func InitHook(cmp *mcmp.Component, hook Hook) {
|
||||||
return WithHook(ctx, start, hook)
|
AddHook(cmp, initEvent, hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start runs all Hooks registered using WithStartHook. This is a special case
|
// Init runs all Hooks registered using InitHook. This is a special case of
|
||||||
// of TriggerHooks.
|
// TriggerHooks.
|
||||||
func Start(ctx context.Context) error {
|
func Init(ctx context.Context, cmp *mcmp.Component) error {
|
||||||
return TriggerHooks(ctx, start)
|
return TriggerHooks(ctx, cmp, initEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithStopHook registers the given Hook to run when Stop is called. This is a
|
// ShutdownHook registers the given Hook to run when Shutdown is called. This is
|
||||||
// special case of WithHook.
|
// a special case of AddHook.
|
||||||
func WithStopHook(ctx context.Context, hook Hook) context.Context {
|
//
|
||||||
return WithHook(ctx, stop, hook)
|
// 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.
|
// which they were registered. This is a special case of TriggerHooks.
|
||||||
func Stop(ctx context.Context) error {
|
func Shutdown(ctx context.Context, cmp *mcmp.Component) error {
|
||||||
return TriggerHooksReverse(ctx, stop)
|
return TriggerHooksReverse(ctx, cmp, shutdownEvent)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
. "testing"
|
. "testing"
|
||||||
|
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
"github.com/mediocregopher/mediocre-go-lib/mcmp"
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
|
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,34 +17,31 @@ func TestHooks(t *T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
cmp := new(mcmp.Component)
|
||||||
ctx = WithHook(ctx, 0, mkHook(1))
|
AddHook(cmp, 0, mkHook(1))
|
||||||
ctx = WithHook(ctx, 0, mkHook(2))
|
AddHook(cmp, 0, mkHook(2))
|
||||||
|
|
||||||
ctxA := mctx.NewChild(ctx, "a")
|
cmpA := cmp.Child("a")
|
||||||
ctxA = WithHook(ctxA, 0, mkHook(3))
|
AddHook(cmpA, 0, mkHook(3))
|
||||||
ctxA = WithHook(ctxA, 999, mkHook(999)) // different key
|
AddHook(cmpA, 999, mkHook(999)) // different key
|
||||||
ctx = mctx.WithChild(ctx, ctxA)
|
|
||||||
|
|
||||||
ctx = WithHook(ctx, 0, mkHook(4))
|
AddHook(cmp, 0, mkHook(4))
|
||||||
|
|
||||||
ctxB := mctx.NewChild(ctx, "b")
|
cmpB := cmp.Child("b")
|
||||||
ctxB = WithHook(ctxB, 0, mkHook(5))
|
AddHook(cmpB, 0, mkHook(5))
|
||||||
ctxB1 := mctx.NewChild(ctxB, "1")
|
cmpB1 := cmpB.Child("1")
|
||||||
ctxB1 = WithHook(ctxB1, 0, mkHook(6))
|
AddHook(cmpB1, 0, mkHook(6))
|
||||||
ctxB = mctx.WithChild(ctxB, ctxB1)
|
|
||||||
ctx = mctx.WithChild(ctx, ctxB)
|
|
||||||
|
|
||||||
ctx = WithHook(ctx, 0, mkHook(7))
|
AddHook(cmp, 0, mkHook(7))
|
||||||
|
|
||||||
massert.Require(t,
|
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),
|
massert.Equal([]int{1, 2, 3, 4, 5, 6, 7}, out),
|
||||||
)
|
)
|
||||||
|
|
||||||
out = nil
|
out = nil
|
||||||
massert.Require(t,
|
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),
|
massert.Equal([]int{7, 6, 5, 4, 3, 2, 1}, out),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
21
mrun/mrun.go
21
mrun/mrun.go
@ -1,12 +1,12 @@
|
|||||||
// Package mrun provides the ability to register callback hooks on contexts, as
|
// Package mrun provides the ability to register callback hooks on Components,
|
||||||
// well as some convenience functions which allow using a context as a
|
// as well as some convenience functions which allow using a context as a
|
||||||
// wait-group.
|
// wait-group.
|
||||||
//
|
//
|
||||||
// Hooks
|
// 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
|
// 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:
|
// they were registered. For example:
|
||||||
//
|
//
|
||||||
// newHook := func(i int) mrun.Hook {
|
// newHook := func(i int) mrun.Hook {
|
||||||
@ -16,15 +16,14 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// ctx := context.Background()
|
// cmp := new(mcmp.Component)
|
||||||
// ctx = mrun.WithStartHook(ctx, newHook(0))
|
// mrun.InitHook(cmp, newHook(0))
|
||||||
//
|
//
|
||||||
// child := mctx.NewChild(ctx, "child")
|
// cmpChild := cmp.Child("child")
|
||||||
// child = mrun.WithStartHook(child, newHook(1))
|
// mrun.InitHook(cmpChild, newHook(1))
|
||||||
// ctx = mctx.WithChild(ctx, child)
|
|
||||||
//
|
//
|
||||||
// ctx = mrun.WithStartHook(ctx, newHook(2))
|
// mrun.InitHook(cmp, newHook(2))
|
||||||
// mrun.Start(ctx) // prints "0", "1", then "2"
|
// mrun.Init(context.Background(), cmp) // prints "0", "1", then "2"
|
||||||
//
|
//
|
||||||
package mrun
|
package mrun
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user