From ae84fa2c9a023b72c4de635b222ff6bad4e734b4 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Tue, 2 Oct 2018 02:08:01 +0000 Subject: [PATCH] flightcontrol: add exponential backoff to Group.Do This patch fixes a stack overflow panic by calling Do in a loop instead of relying on recursion, with an added exponential backoff which errors out if greater than 3 seconds. Signed-off-by: Tibor Vass --- util/flightcontrol/flightcontrol.go | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/util/flightcontrol/flightcontrol.go b/util/flightcontrol/flightcontrol.go index d7923117..89866b76 100644 --- a/util/flightcontrol/flightcontrol.go +++ b/util/flightcontrol/flightcontrol.go @@ -15,7 +15,10 @@ import ( // flightcontrol is like singleflight but with support for cancellation and // nested progress reporting -var errRetry = errors.Errorf("retry") +var ( + errRetry = errors.Errorf("retry") + errRetryTimeout = errors.Errorf("exceeded retry timeout") +) type contextKeyT string @@ -29,12 +32,28 @@ type Group struct { // Do executes a context function syncronized by the key func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (v interface{}, err error) { - defer func() { - if errors.Cause(err) == errRetry { - runtime.Gosched() - v, err = g.Do(ctx, key, fn) + var backoff time.Duration + for { + v, err = g.do(ctx, key, fn) + if err == nil || errors.Cause(err) != errRetry { + return v, err } - }() + // backoff logic + if backoff >= 3*time.Second { + err = errors.Wrapf(errRetryTimeout, "flightcontrol") + return v, err + } + runtime.Gosched() + if backoff > 0 { + time.Sleep(backoff) + backoff *= 2 + } else { + backoff = time.Millisecond + } + } +} + +func (g *Group) do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (interface{}, error) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call)