2023-03-20 20:25:53 +00:00
|
|
|
package retry
|
2022-04-03 04:07:16 +00:00
|
|
|
|
|
|
|
import (
|
2022-08-06 14:21:18 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2022-04-03 04:07:16 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2022-08-06 14:21:18 +00:00
|
|
|
// RetryContext is a basic wrapper around StateChangeConf that will just retry
|
2022-04-03 04:07:16 +00:00
|
|
|
// a function until it no longer returns an error.
|
2022-08-06 14:21:18 +00:00
|
|
|
//
|
|
|
|
// Cancellation from the passed in context will propagate through to the
|
|
|
|
// underlying StateChangeConf
|
|
|
|
func RetryContext(ctx context.Context, timeout time.Duration, f RetryFunc) error {
|
2022-04-03 04:07:16 +00:00
|
|
|
// These are used to pull the error out of the function; need a mutex to
|
|
|
|
// avoid a data race.
|
|
|
|
var resultErr error
|
|
|
|
var resultErrMu sync.Mutex
|
|
|
|
|
|
|
|
c := &StateChangeConf{
|
|
|
|
Pending: []string{"retryableerror"},
|
|
|
|
Target: []string{"success"},
|
|
|
|
Timeout: timeout,
|
|
|
|
MinTimeout: 500 * time.Millisecond,
|
|
|
|
Refresh: func() (interface{}, string, error) {
|
|
|
|
rerr := f()
|
|
|
|
|
|
|
|
resultErrMu.Lock()
|
|
|
|
defer resultErrMu.Unlock()
|
|
|
|
|
|
|
|
if rerr == nil {
|
|
|
|
resultErr = nil
|
|
|
|
return 42, "success", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
resultErr = rerr.Err
|
|
|
|
|
|
|
|
if rerr.Retryable {
|
|
|
|
return 42, "retryableerror", nil
|
|
|
|
}
|
|
|
|
return nil, "quit", rerr.Err
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-08-06 14:21:18 +00:00
|
|
|
_, waitErr := c.WaitForStateContext(ctx)
|
2022-04-03 04:07:16 +00:00
|
|
|
|
|
|
|
// Need to acquire the lock here to be able to avoid race using resultErr as
|
|
|
|
// the return value
|
|
|
|
resultErrMu.Lock()
|
|
|
|
defer resultErrMu.Unlock()
|
|
|
|
|
|
|
|
// resultErr may be nil because the wait timed out and resultErr was never
|
|
|
|
// set; this is still an error
|
|
|
|
if resultErr == nil {
|
|
|
|
return waitErr
|
|
|
|
}
|
|
|
|
// resultErr takes precedence over waitErr if both are set because it is
|
|
|
|
// more likely to be useful
|
|
|
|
return resultErr
|
|
|
|
}
|
|
|
|
|
2022-08-06 14:21:18 +00:00
|
|
|
// Retry is a basic wrapper around StateChangeConf that will just retry
|
|
|
|
// a function until it no longer returns an error.
|
|
|
|
//
|
|
|
|
// Deprecated: Please use RetryContext to ensure proper plugin shutdown
|
|
|
|
func Retry(timeout time.Duration, f RetryFunc) error {
|
|
|
|
return RetryContext(context.Background(), timeout, f)
|
|
|
|
}
|
|
|
|
|
2022-04-03 04:07:16 +00:00
|
|
|
// RetryFunc is the function retried until it succeeds.
|
|
|
|
type RetryFunc func() *RetryError
|
|
|
|
|
|
|
|
// RetryError is the required return type of RetryFunc. It forces client code
|
|
|
|
// to choose whether or not a given error is retryable.
|
|
|
|
type RetryError struct {
|
|
|
|
Err error
|
|
|
|
Retryable bool
|
|
|
|
}
|
|
|
|
|
2022-08-06 14:21:18 +00:00
|
|
|
func (e *RetryError) Unwrap() error {
|
|
|
|
return e.Err
|
|
|
|
}
|
|
|
|
|
2022-04-03 04:07:16 +00:00
|
|
|
// RetryableError is a helper to create a RetryError that's retryable from a
|
2022-08-06 14:21:18 +00:00
|
|
|
// given error. To prevent logic errors, will return an error when passed a
|
|
|
|
// nil error.
|
2022-04-03 04:07:16 +00:00
|
|
|
func RetryableError(err error) *RetryError {
|
|
|
|
if err == nil {
|
2022-08-06 14:21:18 +00:00
|
|
|
return &RetryError{
|
|
|
|
Err: errors.New("empty retryable error received. " +
|
|
|
|
"This is a bug with the Terraform provider and should be " +
|
|
|
|
"reported as a GitHub issue in the provider repository."),
|
|
|
|
Retryable: false,
|
|
|
|
}
|
2022-04-03 04:07:16 +00:00
|
|
|
}
|
|
|
|
return &RetryError{Err: err, Retryable: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NonRetryableError is a helper to create a RetryError that's _not_ retryable
|
2022-08-06 14:21:18 +00:00
|
|
|
// from a given error. To prevent logic errors, will return an error when
|
|
|
|
// passed a nil error.
|
2022-04-03 04:07:16 +00:00
|
|
|
func NonRetryableError(err error) *RetryError {
|
|
|
|
if err == nil {
|
2022-08-06 14:21:18 +00:00
|
|
|
return &RetryError{
|
|
|
|
Err: errors.New("empty non-retryable error received. " +
|
|
|
|
"This is a bug with the Terraform provider and should be " +
|
|
|
|
"reported as a GitHub issue in the provider repository."),
|
|
|
|
Retryable: false,
|
|
|
|
}
|
2022-04-03 04:07:16 +00:00
|
|
|
}
|
|
|
|
return &RetryError{Err: err, Retryable: false}
|
|
|
|
}
|