mcfg: make Child set its hooks to happen _then_, not recursively/concurrently during the parents', otherwise you can't effect a child's behavior with parent configs (e.g. changing the handler being passed into an http server)

This commit is contained in:
Brian Picciano 2018-05-27 07:58:51 +00:00
parent feed62d7d9
commit c5c11dc067

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sync"
"time" "time"
) )
@ -39,9 +38,6 @@ func (h *Hook) Then(h2 Hook) {
} }
} }
// TODO Having Also here might be more confusing than it's worth, since Child
// effectively does the same thing wrt Hook handling
// Also modifies the called upon Hook such that it will perform the original // Also modifies the called upon Hook such that it will perform the original
// functionality at the same time as the given Hook, wait for both to complete, // functionality at the same time as the given Hook, wait for both to complete,
// and return an error if there is one. // and return an error if there is one.
@ -202,7 +198,7 @@ func (c *Cfg) runPreBlock(ctx context.Context, src Source) error {
startCtx, cancel := context.WithTimeout(ctx, c.StartTimeout) startCtx, cancel := context.WithTimeout(ctx, c.StartTimeout)
defer cancel() defer cancel()
return c.startHooks(startCtx) return c.Start(startCtx)
} }
// Run blocks while performing all steps of a Cfg run. The steps, in order, are; // Run blocks while performing all steps of a Cfg run. The steps, in order, are;
@ -225,7 +221,7 @@ func (c *Cfg) Run(ctx context.Context, src Source) error {
stopCtx, cancel := context.WithTimeout(context.Background(), c.StopTimeout) stopCtx, cancel := context.WithTimeout(context.Background(), c.StopTimeout)
defer cancel() defer cancel()
return c.stopHooks(stopCtx) return c.Stop(stopCtx)
} }
// TestRun is like Run, except it's intended to only be used during tests to // TestRun is like Run, except it's intended to only be used during tests to
@ -238,36 +234,6 @@ func (c *Cfg) TestRun() {
} }
} }
func (c *Cfg) startHooks(ctx context.Context) error {
return c.recurseHooks(ctx, func(c *Cfg) Hook { return c.Start })
}
func (c *Cfg) stopHooks(ctx context.Context) error {
return c.recurseHooks(ctx, func(c *Cfg) Hook { return c.Stop })
}
func (c *Cfg) recurseHooks(ctx context.Context, pickHook func(*Cfg) Hook) error {
var wg sync.WaitGroup
wg.Add(len(c.Children))
errCh := make(chan error, len(c.Children))
for name := range c.Children {
childCfg := c.Children[name]
go func() {
defer wg.Done()
if err := childCfg.recurseHooks(ctx, pickHook); err != nil {
errCh <- err
}
}()
}
wg.Wait()
close(errCh)
if err := <-errCh; err != nil {
return err
}
return pickHook(c)(ctx)
}
// Child returns a sub-Cfg of the callee with the given name. The name will be // Child returns a sub-Cfg of the callee with the given name. The name will be
// prepended to all configuration options created in the returned sub-Cfg, and // prepended to all configuration options created in the returned sub-Cfg, and
// must not be empty. // must not be empty.
@ -280,5 +246,7 @@ func (c *Cfg) Child(name string) *Cfg {
c2.Path = append(c2.Path, c.Path...) c2.Path = append(c2.Path, c.Path...)
c2.Path = append(c2.Path, name) c2.Path = append(c2.Path, name)
c.Children[name] = c2 c.Children[name] = c2
c.Start.Then(func(ctx context.Context) error { return c2.Start(ctx) })
c.Stop.Then(func(ctx context.Context) error { return c2.Stop(ctx) })
return c2 return c2
} }