diff --git a/options.go b/options.go index d31b00d..634cf08 100644 --- a/options.go +++ b/options.go @@ -257,10 +257,17 @@ func FullJitterBackoffDelay(n uint, err error, config DelayContext) time.Duratio backoffCeiling = currentCap } - jitter := rand.Int63n(int64(backoffCeiling)) // #nosec G404 -- Using math/rand is acceptable for non-security critical jitter. + jitter := rand.Int63n(saturatingInt64(backoffCeiling)) return time.Duration(jitter) } +func saturatingInt64(positive float64) int64 { + if positive >= float64(math.MaxInt64) { + return math.MaxInt64 + } + return int64(positive) +} + // OnRetry function callback are called each retry // // log each retry example: diff --git a/retry_test.go b/retry_test.go index 0481d24..de6dc3e 100644 --- a/retry_test.go +++ b/retry_test.go @@ -981,6 +981,26 @@ func TestFullJitterBackoffDelay(t *testing.T) { } } +func TestFullJitterBackoffDelayDoesNotPanicWhenCeilingExceedsInt64(t *testing.T) { + tests := []struct { + name string + config *Retrier + }{ + {"no max delay", New(Delay(time.Second))}, + {"max delay at int64 limit", New(Delay(time.Second), MaxDelay(time.Duration(math.MaxInt64)))}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, n := range []uint{0, 33, 34, 63, 64, 1023, 1024, 2000} { + assert.NotPanics(t, func() { + assert.GreaterOrEqual(t, FullJitterBackoffDelay(n, errors.New("test"), tt.config), time.Duration(0), "attempt %d", n) + }, "attempt %d", n) + } + }) + } +} + func TestFullJitterZeroDelayAfterManyAttempts(t *testing.T) { for _, attempts := range []uint{0, 1100} { for _, maxDelay := range []time.Duration{0, time.Second} {