Skip to content

Commit

Permalink
Limit minimal time poll.Wait* functions can wait between attempts (#2543
Browse files Browse the repository at this point in the history
)

Currently calculation of sleep time can make it negative, which
makes `t.C` channel return right away and does not give ctx time
to finish on deadline.
This means that even if context is expired, retries still happen until
there are no more retries.

The fix is to cap minimal sleep time at 5ms to make sure that context
can perform expiration.

NOTE: we could make the minimal sleep smaller, but it's wouldn't make sense
getting less than OS scheduling frequency.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
hairyhum and mergify[bot] committed Dec 19, 2023
1 parent 22391e1 commit 7629583
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 8 deletions.
12 changes: 4 additions & 8 deletions pkg/poll/poll.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ func WaitWithBackoffWithRetries(ctx context.Context, b backoff.Backoff, numRetri
sleep := b.Duration()
if deadline, ok := ctx.Deadline(); ok {
ctxSleep := time.Until(deadline)
sleep = minDuration(sleep, ctxSleep)
// We want to wait for smaller of backoff sleep and context sleep
// but it has to be > 0 to give ctx.Done() a chance
// to return below
sleep = max(min(sleep, ctxSleep), 5*time.Millisecond)
}
t.Reset(sleep)
select {
Expand All @@ -101,10 +104,3 @@ func WaitWithBackoffWithRetries(ctx context.Context, b backoff.Backoff, numRetri
}
}
}

func minDuration(a, b time.Duration) time.Duration {
if a < b {
return a
}
return b
}
26 changes: 26 additions & 0 deletions pkg/poll/poll_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package poll

import (
"context"
"errors"
"fmt"
"runtime"
"testing"
"time"

Expand Down Expand Up @@ -130,6 +132,30 @@ func (s *PollSuite) TestWaitWithBackoffCancellation(c *C) {
c.Check(err, NotNil)
}

func (s *PollSuite) TestWaitWithRetriesTimeout(c *C) {
// There's a better chance of catching a race condition
// if there is only one thread
maxprocs := runtime.GOMAXPROCS(1)
defer runtime.GOMAXPROCS(maxprocs)

f := func(context.Context) (bool, error) {
return false, errors.New("retryable")
}
errf := func(err error) bool {
return err.Error() == "retryable"
}
ctx := context.Background()
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Millisecond)
defer cancel()

backoff := backoff.Backoff{}
backoff.Min = 2 * time.Millisecond
err := WaitWithBackoffWithRetries(ctx, backoff, 10, errf, f)
c.Check(err, NotNil)
c.Assert(err.Error(), Matches, ".*context deadline exceeded*")
}

func (s *PollSuite) TestWaitWithBackoffBackoff(c *C) {
const numIterations = 10
i := 0
Expand Down

0 comments on commit 7629583

Please sign in to comment.