github.com/weaviate/weaviate@v1.24.6/adapters/clients/client.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package clients
    13  
    14  import (
    15  	"bytes"
    16  	"context"
    17  	"fmt"
    18  	"io"
    19  	"net/http"
    20  	"time"
    21  )
    22  
    23  type retryClient struct {
    24  	client *http.Client
    25  	*retryer
    26  }
    27  
    28  func (c *retryClient) doWithCustomMarshaller(timeout time.Duration,
    29  	req *http.Request, body []byte, decode func([]byte) error,
    30  ) (err error) {
    31  	ctx, cancel := context.WithTimeout(req.Context(), timeout)
    32  	defer cancel()
    33  	try := func(ctx context.Context) (bool, error) {
    34  		if body != nil {
    35  			req.Body = io.NopCloser(bytes.NewReader(body))
    36  		}
    37  		res, err := c.client.Do(req)
    38  		if err != nil {
    39  			return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
    40  		}
    41  
    42  		respBody, err := io.ReadAll(res.Body)
    43  		if err != nil {
    44  			return shouldRetry(res.StatusCode), fmt.Errorf("read response: %w", err)
    45  		}
    46  		defer res.Body.Close()
    47  
    48  		if code := res.StatusCode; code != http.StatusOK {
    49  			return shouldRetry(code), fmt.Errorf("status code: %v, error: %s", code, respBody)
    50  		}
    51  
    52  		if err := decode(respBody); err != nil {
    53  			return false, fmt.Errorf("unmarshal response: %w", err)
    54  		}
    55  
    56  		return false, nil
    57  	}
    58  	return c.retry(ctx, 9, try)
    59  }
    60  
    61  type retryer struct {
    62  	minBackOff  time.Duration
    63  	maxBackOff  time.Duration
    64  	timeoutUnit time.Duration
    65  }
    66  
    67  func newRetryer() *retryer {
    68  	return &retryer{
    69  		minBackOff:  time.Millisecond * 250,
    70  		maxBackOff:  time.Second * 30,
    71  		timeoutUnit: time.Second, // used by unit tests
    72  	}
    73  }
    74  
    75  func (r *retryer) retry(ctx context.Context, n int, work func(context.Context) (bool, error)) error {
    76  	delay := r.minBackOff
    77  	for {
    78  		keepTrying, err := work(ctx)
    79  		if !keepTrying || n < 1 || err == nil {
    80  			return err
    81  		}
    82  
    83  		n--
    84  		if delay = backOff(delay); delay > r.maxBackOff {
    85  			delay = r.maxBackOff
    86  		}
    87  		timer := time.NewTimer(delay)
    88  		select {
    89  		case <-ctx.Done():
    90  			timer.Stop()
    91  			return fmt.Errorf("%v: %w", err, ctx.Err())
    92  		case <-timer.C:
    93  		}
    94  		timer.Stop()
    95  	}
    96  }