mcmp: make thread-safe, add InheritedValue, refactor Context/Annotation related methods

This commit is contained in:
Brian Picciano 2019-06-16 19:14:57 -06:00
parent dedf3a0368
commit 7f9b0d5591
4 changed files with 97 additions and 16 deletions

View File

@ -257,7 +257,7 @@ func (cli *SourceCLI) parse(
pvStrValOk = false pvStrValOk = false
} }
if pOk && !pvStrValOk { if pOk && !pvStrValOk {
ctx := mctx.Annotate(p.Component.Annotated(), "param", key) ctx := mctx.Annotate(p.Component.Context(), "param", key)
return nil, merr.New("param expected a value", ctx) return nil, merr.New("param expected a value", ctx)
} }

View File

@ -131,7 +131,7 @@ func Populate(cmp *mcmp.Component, src Source) error {
if !p.Required { if !p.Required {
continue continue
} else if _, ok := pvM[hash]; !ok { } else if _, ok := pvM[hash]; !ok {
ctx := mctx.Annotate(p.Component.Annotated(), ctx := mctx.Annotate(p.Component.Context(),
"param", paramFullName(p.Component.Path(), p.Name)) "param", paramFullName(p.Component.Path(), p.Name))
return merr.New("required parameter is not set", ctx) return merr.New("required parameter is not set", ctx)
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"sync"
"github.com/mediocregopher/mediocre-go-lib/mctx" "github.com/mediocregopher/mediocre-go-lib/mctx"
) )
@ -28,37 +29,76 @@ type child struct {
// A new Component, i.e. the root Component in the hierarchy, can be initialized // A new Component, i.e. the root Component in the hierarchy, can be initialized
// by doing: // by doing:
// new(Component). // new(Component).
//
// Method's on Component are thread-safe.
type Component struct { type Component struct {
l sync.RWMutex
path []string path []string
parent *Component parent *Component
children []child children []child
kv map[interface{}]interface{} kv map[interface{}]interface{}
ctx context.Context
} }
// SetValue sets the given key to the given value on the Component, overwriting // SetValue sets the given key to the given value on the Component, overwriting
// any previous value for that key. // any previous value for that key.
func (c *Component) SetValue(key, value interface{}) { func (c *Component) SetValue(key, value interface{}) {
c.l.Lock()
defer c.l.Unlock()
if c.kv == nil { if c.kv == nil {
c.kv = make(map[interface{}]interface{}, 1) c.kv = make(map[interface{}]interface{}, 1)
} }
c.kv[key] = value c.kv[key] = value
} }
// Value returns the value which has been set for the given key. func (c *Component) value(key interface{}) (interface{}, bool) {
func (c *Component) Value(key interface{}) interface{} { c.l.RLock()
return c.kv[key] defer c.l.RUnlock()
if c.kv == nil {
return nil, false
}
value, ok := c.kv[key]
return value, ok
} }
// Values returns all key/value pairs which have been set via SetValue. The // Value returns the value which has been set for the given key.
// returned map should _not_ be modified. func (c *Component) Value(key interface{}) interface{} {
value, _ := c.value(key)
return value
}
// InheritedValue returns the value which has been set for the given key. It first
// looks for the key on the receiver Component. If not found, it will look on
// its parent Component, and so on, until the key is found. If the key is not
// found on the root Component then false is returned.
func (c *Component) InheritedValue(key interface{}) (interface{}, bool) {
value, ok := c.value(key)
if ok {
return value, ok
} else if c.parent == nil {
return nil, false
}
return c.parent.InheritedValue(key)
}
// Values returns all key/value pairs which have been set via SetValue.
func (c *Component) Values() map[interface{}]interface{} { func (c *Component) Values() map[interface{}]interface{} {
return c.kv c.l.RLock()
defer c.l.RUnlock()
out := make(map[interface{}]interface{}, len(c.kv))
for k, v := range c.kv {
out[k] = v
}
return out
} }
// HasValue returns true if the given key has had a value set on it with // HasValue returns true if the given key has had a value set on it with
// SetValue. // SetValue.
func (c *Component) HasValue(key interface{}) bool { func (c *Component) HasValue(key interface{}) bool {
c.l.RLock()
defer c.l.RUnlock()
_, ok := c.kv[key] _, ok := c.kv[key]
return ok return ok
} }
@ -69,6 +109,8 @@ func (c *Component) HasValue(key interface{}) bool {
// //
// If a child of the given name has already been created this method will panic. // If a child of the given name has already been created this method will panic.
func (c *Component) Child(name string) *Component { func (c *Component) Child(name string) *Component {
c.l.Lock()
defer c.l.Unlock()
for _, child := range c.children { for _, child := range c.children {
if child.name == name { if child.name == name {
panic(fmt.Sprintf("child with name %q already exists", name)) panic(fmt.Sprintf("child with name %q already exists", name))
@ -86,6 +128,8 @@ func (c *Component) Child(name string) *Component {
// Children returns all Components created via the Child method on this // Children returns all Components created via the Child method on this
// Component, in the order they were created. // Component, in the order they were created.
func (c *Component) Children() []*Component { func (c *Component) Children() []*Component {
c.l.RLock()
defer c.l.RUnlock()
children := make([]*Component, len(c.children)) children := make([]*Component, len(c.children))
for i := range c.children { for i := range c.children {
children[i] = c.children[i].Component children[i] = c.children[i].Component
@ -97,6 +141,8 @@ func (c *Component) Children() []*Component {
// or false if this Component was not created via Child (and is therefore the // or false if this Component was not created via Child (and is therefore the
// root Component). // root Component).
func (c *Component) Name() (string, bool) { func (c *Component) Name() (string, bool) {
c.l.RLock()
defer c.l.RUnlock()
if len(c.path) == 0 { if len(c.path) == 0 {
return "", false return "", false
} }
@ -115,11 +161,14 @@ func (c *Component) Name() (string, bool) {
// fmt.Printf("%#v\n", grandChild.Path()) // []string{"child", "grandchild"} // fmt.Printf("%#v\n", grandChild.Path()) // []string{"child", "grandchild"}
// //
func (c *Component) Path() []string { func (c *Component) Path() []string {
c.l.RLock()
defer c.l.RUnlock()
return c.path return c.path
} }
func (c *Component) pathStr() string { func (c *Component) pathStr() string {
path := c.Path() path := make([]string, len(c.path))
copy(path, c.path)
for i := range path { for i := range path {
path[i] = strings.ReplaceAll(path[i], "/", `\/`) path[i] = strings.ReplaceAll(path[i], "/", `\/`)
} }
@ -128,14 +177,28 @@ func (c *Component) pathStr() string {
type annotateKey string type annotateKey string
// Annotate annotates the given Context with information about the Component. func (c *Component) getCtx() context.Context {
func (c *Component) Annotate(ctx context.Context) context.Context { if c.ctx == nil {
return mctx.Annotate(ctx, annotateKey("componentPath"), c.pathStr()) c.ctx = mctx.Annotated(annotateKey("componentPath"), c.pathStr())
}
return c.ctx
} }
// Annotated is a shortcut for `c.Annotate(context.Background())`. // Annotate annotates the Component's internal Context in-place, such that they
func (c *Component) Annotated() context.Context { // will be included in any future calls to the Context method.
return c.Annotate(context.Background()) func (c *Component) Annotate(kv ...interface{}) {
c.l.Lock()
defer c.l.Unlock()
c.ctx = mctx.Annotate(c.getCtx(), kv...)
}
// Context returns a Context which has been annotated with any annotations from
// Annotate calls to this Component, as well as some default annotations which
// are always included.
func (c *Component) Context() context.Context {
c.l.Lock()
defer c.l.Unlock()
return c.getCtx()
} }
// BreadthFirstVisit visits this Component and all of its children, and their // BreadthFirstVisit visits this Component and all of its children, and their

View File

@ -52,6 +52,24 @@ func TestComponent(t *T) {
assertValue(c, "bar", nil), assertValue(c, "bar", nil),
assertValue(child, "bar", 2), assertValue(child, "bar", 2),
) )
assertInheritedValue := func(c *Component, key, expectedValue interface{}) massert.Assertion {
val, ok := c.InheritedValue(key)
return massert.All(
massert.Equal(expectedValue, val),
massert.Equal(expectedValue != nil, ok),
)
}
// test that InheritedValue does what it's supposed to
massert.Require(t,
assertInheritedValue(c, "foo", 1),
assertInheritedValue(child, "foo", 1),
assertInheritedValue(c, "bar", nil),
assertInheritedValue(child, "bar", 2),
assertInheritedValue(c, "xxx", nil),
assertInheritedValue(child, "xxx", nil),
)
} }
func TestBreadFirstVisit(t *T) { func TestBreadFirstVisit(t *T) {
cmp := new(Component) cmp := new(Component)