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 }