open-vault/vendor/github.com/couchbase/gocb/v2/retry.go

195 lines
7.6 KiB
Go

package gocb
import (
"time"
"github.com/couchbase/gocbcore/v9"
)
func translateCoreRetryReasons(reasons []gocbcore.RetryReason) []RetryReason {
var reasonsOut []RetryReason
for _, retryReason := range reasons {
gocbReason, ok := retryReason.(RetryReason)
if !ok {
logErrorf("Failed to assert gocbcore retry reason to gocb retry reason: %v", retryReason)
continue
}
reasonsOut = append(reasonsOut, gocbReason)
}
return reasonsOut
}
// RetryRequest is a request that can possibly be retried.
type RetryRequest interface {
RetryAttempts() uint32
Identifier() string
Idempotent() bool
RetryReasons() []RetryReason
}
type wrappedRetryRequest struct {
req gocbcore.RetryRequest
}
func (req *wrappedRetryRequest) RetryAttempts() uint32 {
return req.req.RetryAttempts()
}
func (req *wrappedRetryRequest) Identifier() string {
return req.req.Identifier()
}
func (req *wrappedRetryRequest) Idempotent() bool {
return req.req.Idempotent()
}
func (req *wrappedRetryRequest) RetryReasons() []RetryReason {
return translateCoreRetryReasons(req.req.RetryReasons())
}
// RetryReason represents the reason for an operation possibly being retried.
type RetryReason interface {
AllowsNonIdempotentRetry() bool
AlwaysRetry() bool
Description() string
}
var (
// UnknownRetryReason indicates that the operation failed for an unknown reason.
UnknownRetryReason = RetryReason(gocbcore.UnknownRetryReason)
// SocketNotAvailableRetryReason indicates that the operation failed because the underlying socket was not available.
SocketNotAvailableRetryReason = RetryReason(gocbcore.SocketNotAvailableRetryReason)
// ServiceNotAvailableRetryReason indicates that the operation failed because the requested service was not available.
ServiceNotAvailableRetryReason = RetryReason(gocbcore.ServiceNotAvailableRetryReason)
// NodeNotAvailableRetryReason indicates that the operation failed because the requested node was not available.
NodeNotAvailableRetryReason = RetryReason(gocbcore.NodeNotAvailableRetryReason)
// KVNotMyVBucketRetryReason indicates that the operation failed because it was sent to the wrong node for the vbucket.
KVNotMyVBucketRetryReason = RetryReason(gocbcore.KVNotMyVBucketRetryReason)
// KVCollectionOutdatedRetryReason indicates that the operation failed because the collection ID on the request is outdated.
KVCollectionOutdatedRetryReason = RetryReason(gocbcore.KVCollectionOutdatedRetryReason)
// KVErrMapRetryReason indicates that the operation failed for an unsupported reason but the KV error map indicated
// that the operation can be retried.
KVErrMapRetryReason = RetryReason(gocbcore.KVErrMapRetryReason)
// KVLockedRetryReason indicates that the operation failed because the document was locked.
KVLockedRetryReason = RetryReason(gocbcore.KVLockedRetryReason)
// KVTemporaryFailureRetryReason indicates that the operation failed because of a temporary failure.
KVTemporaryFailureRetryReason = RetryReason(gocbcore.KVTemporaryFailureRetryReason)
// KVSyncWriteInProgressRetryReason indicates that the operation failed because a sync write is in progress.
KVSyncWriteInProgressRetryReason = RetryReason(gocbcore.KVSyncWriteInProgressRetryReason)
// KVSyncWriteRecommitInProgressRetryReason indicates that the operation failed because a sync write recommit is in progress.
KVSyncWriteRecommitInProgressRetryReason = RetryReason(gocbcore.KVSyncWriteRecommitInProgressRetryReason)
// ServiceResponseCodeIndicatedRetryReason indicates that the operation failed and the service responded stating that
// the request should be retried.
ServiceResponseCodeIndicatedRetryReason = RetryReason(gocbcore.ServiceResponseCodeIndicatedRetryReason)
// SocketCloseInFlightRetryReason indicates that the operation failed because the socket was closed whilst the operation
// was in flight.
SocketCloseInFlightRetryReason = RetryReason(gocbcore.SocketCloseInFlightRetryReason)
// CircuitBreakerOpenRetryReason indicates that the operation failed because the circuit breaker on the connection
// was open.
CircuitBreakerOpenRetryReason = RetryReason(gocbcore.CircuitBreakerOpenRetryReason)
// QueryIndexNotFoundRetryReason indicates that the operation failed to to a missing query index
QueryIndexNotFoundRetryReason = RetryReason(gocbcore.QueryIndexNotFoundRetryReason)
// QueryPreparedStatementFailureRetryReason indicates that the operation failed due to a prepared statement failure
QueryPreparedStatementFailureRetryReason = RetryReason(gocbcore.QueryPreparedStatementFailureRetryReason)
// AnalyticsTemporaryFailureRetryReason indicates that an analytics operation failed due to a temporary failure
AnalyticsTemporaryFailureRetryReason = RetryReason(gocbcore.AnalyticsTemporaryFailureRetryReason)
// SearchTooManyRequestsRetryReason indicates that a search operation failed due to too many requests
SearchTooManyRequestsRetryReason = RetryReason(gocbcore.SearchTooManyRequestsRetryReason)
)
// RetryAction is used by a RetryStrategy to calculate the duration to wait before retrying an operation.
// Returning a value of 0 indicates to not retry.
type RetryAction interface {
Duration() time.Duration
}
// NoRetryRetryAction represents an action that indicates to not retry.
type NoRetryRetryAction struct {
}
// Duration is the length of time to wait before retrying an operation.
func (ra *NoRetryRetryAction) Duration() time.Duration {
return 0
}
// WithDurationRetryAction represents an action that indicates to retry with a given duration.
type WithDurationRetryAction struct {
WithDuration time.Duration
}
// Duration is the length of time to wait before retrying an operation.
func (ra *WithDurationRetryAction) Duration() time.Duration {
return ra.WithDuration
}
// RetryStrategy is to determine if an operation should be retried, and if so how long to wait before retrying.
type RetryStrategy interface {
RetryAfter(req RetryRequest, reason RetryReason) RetryAction
}
func newRetryStrategyWrapper(strategy RetryStrategy) *retryStrategyWrapper {
return &retryStrategyWrapper{
wrapped: strategy,
}
}
type retryStrategyWrapper struct {
wrapped RetryStrategy
}
// RetryAfter calculates and returns a RetryAction describing how long to wait before retrying an operation.
func (rs *retryStrategyWrapper) RetryAfter(req gocbcore.RetryRequest, reason gocbcore.RetryReason) gocbcore.RetryAction {
wreq := &wrappedRetryRequest{
req: req,
}
wrappedAction := rs.wrapped.RetryAfter(wreq, RetryReason(reason))
return gocbcore.RetryAction(wrappedAction)
}
// BackoffCalculator defines how backoff durations will be calculated by the retry API.
type BackoffCalculator func(retryAttempts uint32) time.Duration
// BestEffortRetryStrategy represents a strategy that will keep retrying until it succeeds (or the caller times out
// the request).
type BestEffortRetryStrategy struct {
BackoffCalculator BackoffCalculator
}
// NewBestEffortRetryStrategy returns a new BestEffortRetryStrategy which will use the supplied calculator function
// to calculate retry durations. If calculator is nil then a controlled backoff will be used.
func NewBestEffortRetryStrategy(calculator BackoffCalculator) *BestEffortRetryStrategy {
if calculator == nil {
calculator = BackoffCalculator(gocbcore.ExponentialBackoff(1*time.Millisecond, 500*time.Millisecond, 2))
}
return &BestEffortRetryStrategy{BackoffCalculator: calculator}
}
// RetryAfter calculates and returns a RetryAction describing how long to wait before retrying an operation.
func (rs *BestEffortRetryStrategy) RetryAfter(req RetryRequest, reason RetryReason) RetryAction {
if req.Idempotent() || reason.AllowsNonIdempotentRetry() {
return &WithDurationRetryAction{WithDuration: rs.BackoffCalculator(req.RetryAttempts())}
}
return &NoRetryRetryAction{}
}