-
Notifications
You must be signed in to change notification settings - Fork 3
/
equalizer.go
123 lines (107 loc) · 3.27 KB
/
equalizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package equalizer
import (
"fmt"
"math/big"
"strings"
"sync"
)
// An Equalizer represents an adaptive rate limiter based on a bit array.
//
// The Equalizer uses a round-robin bit array with a moving head to manage
// quotas.
// The quota management algorithm is simple and works in the following way.
// To request a permit in a non-blocking manner use the TryAcquire method.
// The Equalizer will locate the appropriate position on the tape using the
// offset manager and return the value, denoting whether the request is allowed
// or not. To update the tape state, a notification method (Success or Failure)
// should be invoked based on the operation status.
// The Reset and Purge methods allow for immediate transition of the limiter
// state to whether permissive or restrictive.
//
// An Equalizer is safe for use by multiple goroutines simultaneously.
type Equalizer struct {
sync.RWMutex
// tape is the underlying bit array
tape *big.Int
// seed is the initial state of the bit array tape
seed *big.Int
// mask is the positive bitmask
mask *big.Int
// offset is the next index offset manager
offset Offset
// notifyHead is the moving pointer for notifications
notifyHead int
// adjustable is the number of unmasked bits
adjustable int
}
// NewEqualizer instantiates and returns a new Equalizer rate limiter, where
// size is the length of the bit array, reserved is the number of reserved
// positive bits and offset is an instance of the equalizer.Offset strategy.
func NewEqualizer(size, reserved int, offset Offset) (*Equalizer, error) {
if offset == nil {
return nil, fmt.Errorf("offset is nil")
}
if size < 1 {
return nil, fmt.Errorf("nonpositive size: %d", size)
}
if reserved < 1 {
return nil, fmt.Errorf("nonpositive reserved: %d", reserved)
}
if reserved > size {
return nil, fmt.Errorf("reserved must not exceed size")
}
// init the seed bit array
var seed big.Int
seed.SetString(strings.Repeat("1", size), 2)
// init the positive bitmask
var mask big.Int
mask.SetString(strings.Repeat("1", reserved), 2)
mask.Lsh(&mask, uint(size-reserved))
// init the operational bit array tape
var tape big.Int
tape.Set(&seed)
return &Equalizer{
tape: &tape,
seed: &seed,
mask: &mask,
offset: offset,
adjustable: size - reserved,
}, nil
}
// TryAcquire moves the tape head to the next index and returns the value.
func (eq *Equalizer) TryAcquire() bool {
eq.RLock()
defer eq.RUnlock()
head := eq.offset.NextIndex()
return eq.tape.Bit(head) > 0
}
// Success notifies the equalizer with n successful operations.
func (eq *Equalizer) Success(n int) {
eq.notify(n, 1)
}
// Failure notifies the equalizer with n failed operations.
func (eq *Equalizer) Failure(n int) {
eq.notify(n, 0)
}
// notify advances the notification head by n bits, assigning the given value.
func (eq *Equalizer) notify(n int, value uint) {
eq.Lock()
defer eq.Unlock()
for i := 0; i < n; i++ {
pos := eq.notifyHead % eq.adjustable
eq.tape.SetBit(eq.tape, pos, value)
eq.notifyHead++
}
}
// Reset resets the tape to its initial state.
func (eq *Equalizer) Reset() {
eq.Lock()
defer eq.Unlock()
eq.tape.Set(eq.seed)
}
// Purge blanks out the tape to the positive bitmask state.
func (eq *Equalizer) Purge() {
eq.Lock()
defer eq.Unlock()
eq.tape.Set(eq.mask)
}