...

Text file src/go.opentelemetry.io/otel/internal/shared/otlp/retry/retry.go.tmpl

Documentation: go.opentelemetry.io/otel/internal/shared/otlp/retry

     1// Code created by gotmpl. DO NOT MODIFY.
     2// source: internal/shared/otlp/retry/retry.go.tmpl
     3
     4// Copyright The OpenTelemetry Authors
     5//
     6// Licensed under the Apache License, Version 2.0 (the "License");
     7// you may not use this file except in compliance with the License.
     8// You may obtain a copy of the License at
     9//
    10//     http://www.apache.org/licenses/LICENSE-2.0
    11//
    12// Unless required by applicable law or agreed to in writing, software
    13// distributed under the License is distributed on an "AS IS" BASIS,
    14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15// See the License for the specific language governing permissions and
    16// limitations under the License.
    17
    18// Package retry provides request retry functionality that can perform
    19// configurable exponential backoff for transient errors and honor any
    20// explicit throttle responses received.
    21package retry
    22
    23import (
    24	"context"
    25	"fmt"
    26	"time"
    27
    28	"github.com/cenkalti/backoff/v4"
    29)
    30
    31// DefaultConfig are the recommended defaults to use.
    32var DefaultConfig = Config{
    33	Enabled:         true,
    34	InitialInterval: 5 * time.Second,
    35	MaxInterval:     30 * time.Second,
    36	MaxElapsedTime:  time.Minute,
    37}
    38
    39// Config defines configuration for retrying batches in case of export failure
    40// using an exponential backoff.
    41type Config struct {
    42	// Enabled indicates whether to not retry sending batches in case of
    43	// export failure.
    44	Enabled bool
    45	// InitialInterval the time to wait after the first failure before
    46	// retrying.
    47	InitialInterval time.Duration
    48	// MaxInterval is the upper bound on backoff interval. Once this value is
    49	// reached the delay between consecutive retries will always be
    50	// `MaxInterval`.
    51	MaxInterval time.Duration
    52	// MaxElapsedTime is the maximum amount of time (including retries) spent
    53	// trying to send a request/batch.  Once this value is reached, the data
    54	// is discarded.
    55	MaxElapsedTime time.Duration
    56}
    57
    58// RequestFunc wraps a request with retry logic.
    59type RequestFunc func(context.Context, func(context.Context) error) error
    60
    61// EvaluateFunc returns if an error is retry-able and if an explicit throttle
    62// duration should be honored that was included in the error.
    63//
    64// The function must return true if the error argument is retry-able,
    65// otherwise it must return false for the first return parameter.
    66//
    67// The function must return a non-zero time.Duration if the error contains
    68// explicit throttle duration that should be honored, otherwise it must return
    69// a zero valued time.Duration.
    70type EvaluateFunc func(error) (bool, time.Duration)
    71
    72// RequestFunc returns a RequestFunc using the evaluate function to determine
    73// if requests can be retried and based on the exponential backoff
    74// configuration of c.
    75func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
    76	if !c.Enabled {
    77		return func(ctx context.Context, fn func(context.Context) error) error {
    78			return fn(ctx)
    79		}
    80	}
    81
    82	return func(ctx context.Context, fn func(context.Context) error) error {
    83		// Do not use NewExponentialBackOff since it calls Reset and the code here
    84		// must call Reset after changing the InitialInterval (this saves an
    85		// unnecessary call to Now).
    86		b := &backoff.ExponentialBackOff{
    87			InitialInterval:     c.InitialInterval,
    88			RandomizationFactor: backoff.DefaultRandomizationFactor,
    89			Multiplier:          backoff.DefaultMultiplier,
    90			MaxInterval:         c.MaxInterval,
    91			MaxElapsedTime:      c.MaxElapsedTime,
    92			Stop:                backoff.Stop,
    93			Clock:               backoff.SystemClock,
    94		}
    95		b.Reset()
    96
    97		for {
    98			err := fn(ctx)
    99			if err == nil {
   100				return nil
   101			}
   102
   103			retryable, throttle := evaluate(err)
   104			if !retryable {
   105				return err
   106			}
   107
   108			bOff := b.NextBackOff()
   109			if bOff == backoff.Stop {
   110				return fmt.Errorf("max retry time elapsed: %w", err)
   111			}
   112
   113			// Wait for the greater of the backoff or throttle delay.
   114			var delay time.Duration
   115			if bOff > throttle {
   116				delay = bOff
   117			} else {
   118				elapsed := b.GetElapsedTime()
   119				if b.MaxElapsedTime != 0 && elapsed+throttle > b.MaxElapsedTime {
   120					return fmt.Errorf("max retry time would elapse: %w", err)
   121				}
   122				delay = throttle
   123			}
   124
   125			if ctxErr := waitFunc(ctx, delay); ctxErr != nil {
   126				return fmt.Errorf("%w: %s", ctxErr, err)
   127			}
   128		}
   129	}
   130}
   131
   132// Allow override for testing.
   133var waitFunc = wait
   134
   135// wait takes the caller's context, and the amount of time to wait.  It will
   136// return nil if the timer fires before or at the same time as the context's
   137// deadline.  This indicates that the call can be retried.
   138func wait(ctx context.Context, delay time.Duration) error {
   139	timer := time.NewTimer(delay)
   140	defer timer.Stop()
   141
   142	select {
   143	case <-ctx.Done():
   144		// Handle the case where the timer and context deadline end
   145		// simultaneously by prioritizing the timer expiration nil value
   146		// response.
   147		select {
   148		case <-timer.C:
   149		default:
   150			return ctx.Err()
   151		}
   152	case <-timer.C:
   153	}
   154
   155	return nil
   156}

View as plain text