mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
101 lines
2.1 KiB
Go
101 lines
2.1 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package cloud
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// Fatal implements a RetryBackoff func return value that, if encountered,
|
|
// signals that the func should not be retried. In that case, the error
|
|
// returned by the interface method will be returned by RetryBackoff
|
|
type Fatal interface {
|
|
FatalError() error
|
|
}
|
|
|
|
// NonRetryableError is a simple implementation of Fatal that wraps an error
|
|
type NonRetryableError struct {
|
|
InnerError error
|
|
}
|
|
|
|
// FatalError returns the inner error, but also implements Fatal, which
|
|
// signals to RetryBackoff that a non-retryable error occurred.
|
|
func (e NonRetryableError) FatalError() error {
|
|
return e.InnerError
|
|
}
|
|
|
|
// Error returns the inner error string
|
|
func (e NonRetryableError) Error() string {
|
|
return e.InnerError.Error()
|
|
}
|
|
|
|
var (
|
|
initialBackoffDelay = time.Second
|
|
maxBackoffDelay = 3 * time.Second
|
|
)
|
|
|
|
// RetryBackoff retries function f until nil or a FatalError is returned.
|
|
// RetryBackoff only returns an error if the context is in error or if a
|
|
// FatalError was encountered.
|
|
func RetryBackoff(ctx context.Context, f func() error) error {
|
|
// doneCh signals that the routine is done and sends the last error
|
|
var doneCh = make(chan struct{})
|
|
var errVal atomic.Value
|
|
type errWrap struct {
|
|
E error
|
|
}
|
|
|
|
go func() {
|
|
// the retry delay between each attempt
|
|
var delay time.Duration = 0
|
|
defer close(doneCh)
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(delay):
|
|
}
|
|
|
|
err := f()
|
|
switch e := err.(type) {
|
|
case nil:
|
|
return
|
|
case Fatal:
|
|
errVal.Store(errWrap{e.FatalError()})
|
|
return
|
|
}
|
|
|
|
delay *= 2
|
|
if delay == 0 {
|
|
delay = initialBackoffDelay
|
|
}
|
|
|
|
delay = min(delay, maxBackoffDelay)
|
|
|
|
log.Printf("[WARN] retryable error: %q, delaying for %s", err, delay)
|
|
}
|
|
}()
|
|
|
|
// Wait until done or deadline
|
|
select {
|
|
case <-doneCh:
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
err, hadErr := errVal.Load().(errWrap)
|
|
var lastErr error
|
|
if hadErr {
|
|
lastErr = err.E
|
|
}
|
|
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
return lastErr
|
|
}
|