mirror of
https://github.com/hanzoai/vector-go.git
synced 2026-10-30 18:18:35 +00:00
103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package qdrant
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"math/rand/v2"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
codes "google.golang.org/grpc/codes"
|
|
status "google.golang.org/grpc/status"
|
|
)
|
|
|
|
const (
|
|
defaultBaseBackoff = 100 * time.Millisecond
|
|
defaultMaxBackoff = 5 * time.Second
|
|
)
|
|
|
|
// RetryConfig controls automatic retry behavior for transient gRPC failures.
|
|
// When set on Config, a unary interceptor is registered that retries calls
|
|
// receiving ResourceExhausted or Unavailable status codes.
|
|
type RetryConfig struct {
|
|
// MaxRetries is the maximum number of retry attempts.
|
|
// Zero means no retries.
|
|
MaxRetries uint
|
|
// BaseBackoff is the initial wait duration before the first retry.
|
|
// Defaults to 100ms if zero.
|
|
BaseBackoff time.Duration
|
|
// MaxBackoff caps the backoff duration between retries.
|
|
// Defaults to 5s if zero.
|
|
MaxBackoff time.Duration
|
|
}
|
|
|
|
func (rc *RetryConfig) baseBackoff() time.Duration {
|
|
if rc.BaseBackoff > 0 {
|
|
return rc.BaseBackoff
|
|
}
|
|
return defaultBaseBackoff
|
|
}
|
|
|
|
func (rc *RetryConfig) maxBackoff() time.Duration {
|
|
if rc.MaxBackoff > 0 {
|
|
return rc.MaxBackoff
|
|
}
|
|
return defaultMaxBackoff
|
|
}
|
|
|
|
func (rc *RetryConfig) retryInterceptor() grpc.UnaryClientInterceptor {
|
|
return func(
|
|
ctx context.Context,
|
|
method string,
|
|
req, reply any,
|
|
cc *grpc.ClientConn,
|
|
invoker grpc.UnaryInvoker,
|
|
opts ...grpc.CallOption,
|
|
) error {
|
|
var err error
|
|
for attempt := range rc.MaxRetries + 1 {
|
|
err = invoker(ctx, method, req, reply, cc, opts...)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if attempt == rc.MaxRetries {
|
|
break
|
|
}
|
|
if !isRetryable(err) {
|
|
return err
|
|
}
|
|
backoff := rc.backoffDuration(attempt)
|
|
timer := time.NewTimer(backoff)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return ctx.Err()
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
func isRetryable(err error) bool {
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
return false
|
|
}
|
|
code := st.Code()
|
|
return code == codes.ResourceExhausted || code == codes.Unavailable
|
|
}
|
|
|
|
const backoffBase = 2.0
|
|
|
|
// backoffDuration computes an exponential backoff with full jitter.
|
|
func (rc *RetryConfig) backoffDuration(attempt uint) time.Duration {
|
|
exp := math.Pow(backoffBase, float64(attempt))
|
|
backoff := min(time.Duration(float64(rc.baseBackoff())*exp), rc.maxBackoff())
|
|
// Full jitter: uniform random in [0, backoff).
|
|
if backoff > 0 {
|
|
backoff = time.Duration(rand.Int64N(int64(backoff)))
|
|
}
|
|
return backoff
|
|
}
|