diff --git a/example_test.go b/example_test.go index f9f686b..b18ff07 100644 --- a/example_test.go +++ b/example_test.go @@ -2,7 +2,10 @@ package backoff import ( "context" + "errors" "log" + "sync" + "time" ) func ExampleRetry() { @@ -40,6 +43,60 @@ func ExampleRetryContext() { // Operation is successful. } +func ExampleThreadSafe() { + backoff := NewExponentialBackOff() + + backoff.MaxElapsedTime = time.Millisecond * 500 + backoff.MaxInterval = time.Millisecond * 200 + + wg := sync.WaitGroup{} + wg.Add(2) + + failTimes := 3 + + go func() { + tries := 0 + + err := Retry(func() error { + if tries >= failTimes { + return nil + } + + tries++ + return errors.New("FAILED") + }, backoff) + + if err != nil { + // Handle error. + } + + // Operation is successful. + wg.Done() + }() + + go func() { + tries := 0 + + err := Retry(func() error { + if tries >= failTimes { + return nil + } + + tries++ + return errors.New("FAILED") + }, backoff) + + if err != nil { + // Handle error. + } + + // Operation is successful. + wg.Done() + }() + + wg.Wait() +} + func ExampleTicker() { // An operation that may fail. operation := func() error { diff --git a/exponential.go b/exponential.go index a031a65..227d80f 100644 --- a/exponential.go +++ b/exponential.go @@ -2,6 +2,7 @@ package backoff import ( "math/rand" + "sync" "time" ) @@ -63,6 +64,7 @@ type ExponentialBackOff struct { currentInterval time.Duration startTime time.Time + mutex *sync.Mutex } // Clock is an interface that returns current time for BackOff. @@ -88,6 +90,7 @@ func NewExponentialBackOff() *ExponentialBackOff { MaxInterval: DefaultMaxInterval, MaxElapsedTime: DefaultMaxElapsedTime, Clock: SystemClock, + mutex: &sync.Mutex{}, } b.Reset() return b @@ -104,6 +107,9 @@ var SystemClock = systemClock{} // Reset the interval back to the initial retry interval and restarts the timer. func (b *ExponentialBackOff) Reset() { + b.mutex.Lock() + defer b.mutex.Unlock() + b.currentInterval = b.InitialInterval b.startTime = b.Clock.Now() } @@ -111,6 +117,9 @@ func (b *ExponentialBackOff) Reset() { // NextBackOff calculates the next backoff interval using the formula: // Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval) func (b *ExponentialBackOff) NextBackOff() time.Duration { + b.mutex.Lock() + defer b.mutex.Unlock() + // Make sure we have not gone over the maximum elapsed time. if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime { return Stop