...

Source file src/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry/retry.go

Documentation: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/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.
    21  package retry // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"time"
    27  
    28  	"github.com/cenkalti/backoff/v4"
    29  )
    30  
    31  // DefaultConfig are the recommended defaults to use.
    32  var 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.
    41  type 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.
    59  type 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.
    70  type 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.
    75  func (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.
   133  var 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.
   138  func 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  }
   157  

View as plain text