github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/retry/budget/budget.go (about)

     1  package budget
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/jonboulle/clockwork"
     9  
    10  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xrand"
    12  )
    13  
    14  type (
    15  	// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
    16  	Budget interface {
    17  		// Acquire will called on second and subsequent retry attempts
    18  		Acquire(ctx context.Context) error
    19  	}
    20  	fixedBudget struct {
    21  		clock  clockwork.Clock
    22  		ticker clockwork.Ticker
    23  		quota  chan struct{}
    24  		done   chan struct{}
    25  	}
    26  	fixedBudgetOption func(q *fixedBudget)
    27  	percentBudget     struct {
    28  		percent int
    29  		rand    xrand.Rand
    30  	}
    31  )
    32  
    33  func withFixedBudgetClock(clock clockwork.Clock) fixedBudgetOption {
    34  	return func(q *fixedBudget) {
    35  		q.clock = clock
    36  	}
    37  }
    38  
    39  // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
    40  func Limited(attemptsPerSecond int, opts ...fixedBudgetOption) *fixedBudget {
    41  	q := &fixedBudget{
    42  		clock: clockwork.NewRealClock(),
    43  		done:  make(chan struct{}),
    44  	}
    45  	for _, opt := range opts {
    46  		opt(q)
    47  	}
    48  	if attemptsPerSecond <= 0 {
    49  		q.quota = make(chan struct{})
    50  		close(q.quota)
    51  	} else {
    52  		q.quota = make(chan struct{}, attemptsPerSecond)
    53  		for range make([]struct{}, attemptsPerSecond) {
    54  			q.quota <- struct{}{}
    55  		}
    56  		q.ticker = q.clock.NewTicker(time.Second / time.Duration(attemptsPerSecond))
    57  		go func() {
    58  			defer close(q.quota)
    59  			for {
    60  				select {
    61  				case <-q.ticker.Chan():
    62  					select {
    63  					case q.quota <- struct{}{}:
    64  					case <-q.done:
    65  						return
    66  					}
    67  				case <-q.done:
    68  					return
    69  				}
    70  			}
    71  		}()
    72  	}
    73  
    74  	return q
    75  }
    76  
    77  // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
    78  func (q *fixedBudget) Stop() {
    79  	if q.ticker != nil {
    80  		q.ticker.Stop()
    81  	}
    82  	close(q.done)
    83  }
    84  
    85  // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
    86  func (q *fixedBudget) Acquire(ctx context.Context) error {
    87  	if err := ctx.Err(); err != nil {
    88  		return xerrors.WithStackTrace(err)
    89  	}
    90  	select {
    91  	case <-q.done:
    92  		return xerrors.WithStackTrace(errClosedBudget)
    93  	case <-q.quota:
    94  		return nil
    95  	case <-ctx.Done():
    96  		return xerrors.WithStackTrace(ctx.Err())
    97  	}
    98  }
    99  
   100  // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
   101  func Percent(percent int) *percentBudget {
   102  	if percent > 100 || percent < 0 {
   103  		panic(fmt.Sprintf("wrong percent value: %d", percent))
   104  	}
   105  
   106  	return &percentBudget{
   107  		percent: percent,
   108  		rand:    xrand.New(xrand.WithLock()),
   109  	}
   110  }
   111  
   112  func (b *percentBudget) Acquire(ctx context.Context) error {
   113  	if b.rand.Int(100) < b.percent { //nolint:gomnd
   114  		return nil
   115  	}
   116  
   117  	return ErrNoQuota
   118  }