@@ -19,7 +19,6 @@ package kncloudevents
1919import (
2020 "context"
2121 "fmt"
22- "math"
2322 "net/http"
2423 "strconv"
2524 "time"
@@ -58,11 +57,12 @@ type Backoff func(attemptNum int, resp *http.Response) time.Duration
5857type RetryConfig struct {
5958 // Maximum number of retries
6059 RetryMax int
61- // These next two variables are just copied from the original DeliverySpec so
60+ // These next three variables are just copied from the original DeliverySpec so
6261 // we can detect if anything has changed. We can not do that with the CheckRetry
6362 // Backoff (at least not easily).
6463 BackoffDelay * string
6564 BackoffPolicy * v1.BackoffPolicyType
65+ BackoffMax * string
6666
6767 CheckRetry CheckRetry
6868 Backoff Backoff
@@ -92,6 +92,20 @@ func RetryConfigFromDeliverySpec(spec v1.DeliverySpec) (RetryConfig, error) {
9292 }
9393 retryConfig .BackoffPolicy = spec .BackoffPolicy
9494 retryConfig .BackoffDelay = spec .BackoffDelay
95+ retryConfig .BackoffMax = spec .BackoffMax
96+
97+ var backoffMaxDuration * time.Duration
98+ if spec .BackoffMax != nil {
99+ maxPeriod , err := period .Parse (* spec .BackoffMax )
100+ if err != nil || maxPeriod .IsZero () || maxPeriod .IsNegative () {
101+ if err != nil {
102+ return retryConfig , fmt .Errorf ("failed to parse Spec.BackoffMax: %w" , err )
103+ }
104+ return retryConfig , fmt .Errorf ("Spec.BackoffMax must be greater than zero" )
105+ }
106+ maxDelay := saturatingPeriodDuration (maxPeriod )
107+ backoffMaxDuration = & maxDelay
108+ }
95109
96110 if spec .BackoffPolicy != nil && spec .BackoffDelay != nil {
97111
@@ -100,15 +114,15 @@ func RetryConfigFromDeliverySpec(spec v1.DeliverySpec) (RetryConfig, error) {
100114 return retryConfig , fmt .Errorf ("failed to parse Spec.BackoffDelay: %w" , err )
101115 }
102116
103- delayDuration , _ := delay . Duration ( )
117+ delayDuration := saturatingPeriodDuration ( delay )
104118 switch * spec .BackoffPolicy {
105119 case v1 .BackoffPolicyExponential :
106120 retryConfig .Backoff = func (attemptNum int , resp * http.Response ) time.Duration {
107- return delayDuration * time . Duration ( math . Exp2 ( float64 ( attemptNum )) )
121+ return exponentialBackoff ( delayDuration , attemptNum , backoffMaxDuration )
108122 }
109123 case v1 .BackoffPolicyLinear :
110124 retryConfig .Backoff = func (attemptNum int , resp * http.Response ) time.Duration {
111- return delayDuration * time . Duration ( attemptNum )
125+ return linearBackoff ( delayDuration , attemptNum , backoffMaxDuration )
112126 }
113127 }
114128 }
@@ -133,6 +147,55 @@ func RetryConfigFromDeliverySpec(spec v1.DeliverySpec) (RetryConfig, error) {
133147 return retryConfig , nil
134148}
135149
150+ const maxBackoffDuration = time .Duration (1 << 63 - 1 )
151+
152+ func saturatingPeriodDuration (p period.Period ) time.Duration {
153+ if p .IsPositive () && p .TotalDaysApprox () > int (maxBackoffDuration / (24 * time .Hour )) {
154+ return maxBackoffDuration
155+ }
156+
157+ d , _ := p .Duration ()
158+ if p .IsPositive () && d < 0 {
159+ return maxBackoffDuration
160+ }
161+ return d
162+ }
163+
164+ func linearBackoff (delay time.Duration , attemptNum int , configuredMax * time.Duration ) time.Duration {
165+ if attemptNum <= 0 || delay <= 0 {
166+ return 0
167+ }
168+ limit := maxBackoffDuration
169+ if configuredMax != nil {
170+ limit = * configuredMax
171+ }
172+ if delay >= limit || time .Duration (attemptNum ) > limit / delay {
173+ return limit
174+ }
175+ return delay * time .Duration (attemptNum )
176+ }
177+
178+ func exponentialBackoff (delay time.Duration , attemptNum int , configuredMax * time.Duration ) time.Duration {
179+ if attemptNum < 0 || delay <= 0 {
180+ return 0
181+ }
182+ limit := maxBackoffDuration
183+ if configuredMax != nil {
184+ limit = * configuredMax
185+ }
186+ if delay >= limit {
187+ return limit
188+ }
189+ result := delay
190+ for range attemptNum {
191+ if result > limit / 2 {
192+ return limit
193+ }
194+ result *= 2
195+ }
196+ return result
197+ }
198+
136199// SelectiveRetry is an alternative function to determine whether to retry based on response
137200//
138201// Note - Returning true indicates a retry should occur. Returning an error will result in that
0 commit comments