mrun: move Hook code into hook.go
This commit is contained in:
parent
0fb0cb92a0
commit
448008cb7c
124
mrun/hook.go
Normal file
124
mrun/hook.go
Normal file
@ -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)
|
||||||
|
}
|
54
mrun/hook_test.go
Normal file
54
mrun/hook_test.go
Normal file
@ -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),
|
||||||
|
))
|
||||||
|
}
|
121
mrun/mrun.go
121
mrun/mrun.go
@ -95,124 +95,3 @@ func Wait(ctx mctx.Context, cancelCh <-chan struct{}) error {
|
|||||||
|
|
||||||
return nil
|
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)
|
|
||||||
}
|
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestThreadWait(t *T) {
|
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),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user