|
1 | 1 | package limiter
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "container/list" |
5 |
| - "context" |
6 |
| - "fmt" |
7 |
| - "sync" |
8 | 4 | "time"
|
9 | 5 |
|
10 | 6 | "github.com/platinummonkey/go-concurrency-limits/core"
|
11 | 7 | )
|
12 | 8 |
|
13 |
| -type fifoElement struct { |
14 |
| - ctx context.Context |
15 |
| - releaseChan chan core.Listener |
16 |
| -} |
17 |
| - |
18 |
| -func (e *fifoElement) setListener(listener core.Listener) { |
19 |
| - select { |
20 |
| - case e.releaseChan <- listener: |
21 |
| - // noop |
22 |
| - default: |
23 |
| - // timeout has expired |
24 |
| - } |
25 |
| -} |
26 |
| - |
27 |
| -type fifoQueue struct { |
28 |
| - q *list.List |
29 |
| - mu sync.RWMutex |
30 |
| -} |
31 |
| - |
32 |
| -func (q *fifoQueue) len() int { |
33 |
| - q.mu.RLock() |
34 |
| - defer q.mu.RUnlock() |
35 |
| - return q.q.Len() |
36 |
| -} |
37 |
| - |
38 |
| -func (q *fifoQueue) push(ctx context.Context) (*list.Element, chan core.Listener) { |
39 |
| - q.mu.Lock() |
40 |
| - defer q.mu.Unlock() |
41 |
| - releaseChan := make(chan core.Listener, 1) |
42 |
| - fe := fifoElement{ |
43 |
| - ctx: ctx, |
44 |
| - releaseChan: releaseChan, |
45 |
| - } |
46 |
| - e := q.q.PushBack(fe) |
47 |
| - return e, releaseChan |
48 |
| -} |
49 |
| - |
50 |
| -func (q *fifoQueue) pop() *fifoElement { |
51 |
| - q.mu.Lock() |
52 |
| - defer q.mu.Unlock() |
53 |
| - e := q.q.Front() |
54 |
| - if e != nil { |
55 |
| - fe := e.Value.(fifoElement) |
56 |
| - q.q.Remove(e) |
57 |
| - return &fe |
58 |
| - } |
59 |
| - return nil |
60 |
| -} |
61 |
| - |
62 |
| -func (q *fifoQueue) peek() *fifoElement { |
63 |
| - q.mu.RLock() |
64 |
| - defer q.mu.RUnlock() |
65 |
| - e := q.q.Front() |
66 |
| - if e != nil { |
67 |
| - fe := q.q.Front().Value.(fifoElement) |
68 |
| - return &fe |
69 |
| - } |
70 |
| - return nil |
71 |
| -} |
72 |
| - |
73 |
| -func (q *fifoQueue) remove(event *list.Element) { |
74 |
| - if event == nil { |
75 |
| - return |
76 |
| - } |
77 |
| - q.mu.Lock() |
78 |
| - defer q.mu.Unlock() |
79 |
| - q.q.Remove(event) |
80 |
| -} |
81 |
| - |
82 |
| -// FifoBlockingListener implements a blocking listener for the FifoBlockingListener |
83 |
| -type FifoBlockingListener struct { |
84 |
| - delegateListener core.Listener |
85 |
| - limiter *FifoBlockingLimiter |
86 |
| -} |
87 |
| - |
88 |
| -func (l *FifoBlockingListener) unblock() { |
89 |
| - l.limiter.mu.Lock() |
90 |
| - defer l.limiter.mu.Unlock() |
91 |
| - if l.limiter.backlog.len() > 0 { |
92 |
| - event := l.limiter.backlog.peek() |
93 |
| - if event != nil { |
94 |
| - listener, ok := l.limiter.delegate.Acquire(event.ctx) |
95 |
| - if ok && listener != nil { |
96 |
| - nextEvent := l.limiter.backlog.pop() |
97 |
| - nextEvent.setListener(listener) |
98 |
| - } |
99 |
| - } |
100 |
| - // otherwise: still can't acquire the limit. unblock will be called again next time the limit is released. |
101 |
| - } |
102 |
| -} |
103 |
| - |
104 |
| -// OnDropped is called to indicate the request failed and was dropped due to being rejected by an external limit or |
105 |
| -// hitting a timeout. Loss based Limit implementations will likely do an aggressive reducing in limit when this |
106 |
| -// happens. |
107 |
| -func (l *FifoBlockingListener) OnDropped() { |
108 |
| - l.delegateListener.OnDropped() |
109 |
| - l.unblock() |
110 |
| -} |
111 |
| - |
112 |
| -// OnIgnore is called to indicate the operation failed before any meaningful RTT measurement could be made and |
113 |
| -// should be ignored to not introduce an artificially low RTT. |
114 |
| -func (l *FifoBlockingListener) OnIgnore() { |
115 |
| - l.delegateListener.OnIgnore() |
116 |
| - l.unblock() |
117 |
| -} |
118 |
| - |
119 |
| -// OnSuccess is called as a notification that the operation succeeded and internally measured latency should be |
120 |
| -// used as an RTT sample. |
121 |
| -func (l *FifoBlockingListener) OnSuccess() { |
122 |
| - l.delegateListener.OnSuccess() |
123 |
| - l.unblock() |
124 |
| -} |
125 |
| - |
126 | 9 | // FifoBlockingLimiter implements a Limiter that blocks the caller when the limit has been reached. This strategy
|
127 | 10 | // ensures the resource is properly protected but favors availability over latency by not fast failing requests when
|
128 | 11 | // the limit has been reached. To help keep success latencies low and minimize timeouts any blocked requests are
|
129 | 12 | // processed in last in/first out order.
|
130 | 13 | //
|
131 | 14 | // Use this limiter only when the concurrency model allows the limiter to be blocked.
|
| 15 | +// Deprecated in favor of QueueBlockingLimiter |
132 | 16 | type FifoBlockingLimiter struct {
|
133 |
| - delegate core.Limiter |
134 |
| - maxBacklogSize int |
135 |
| - maxBacklogTimeout time.Duration |
136 |
| - |
137 |
| - backlog fifoQueue |
138 |
| - c *sync.Cond |
139 |
| - mu sync.RWMutex |
| 17 | + *QueueBlockingLimiter |
140 | 18 | }
|
141 | 19 |
|
142 | 20 | // NewFifoBlockingLimiter will create a new FifoBlockingLimiter
|
| 21 | +// Deprecated, use NewQueueBlockingLimiterFromConfig instead |
143 | 22 | func NewFifoBlockingLimiter(
|
144 | 23 | delegate core.Limiter,
|
145 | 24 | maxBacklogSize int,
|
146 | 25 | maxBacklogTimeout time.Duration,
|
147 | 26 | ) *FifoBlockingLimiter {
|
148 |
| - if maxBacklogSize <= 0 { |
149 |
| - maxBacklogSize = 100 |
150 |
| - } |
151 |
| - if maxBacklogTimeout == 0 { |
152 |
| - maxBacklogTimeout = time.Millisecond * 1000 |
153 |
| - } |
154 |
| - mu := sync.Mutex{} |
| 27 | + |
155 | 28 | return &FifoBlockingLimiter{
|
156 |
| - delegate: delegate, |
157 |
| - maxBacklogSize: maxBacklogSize, |
158 |
| - maxBacklogTimeout: maxBacklogTimeout, |
159 |
| - backlog: fifoQueue{ |
160 |
| - q: list.New(), |
161 |
| - }, |
162 |
| - c: sync.NewCond(&mu), |
| 29 | + NewQueueBlockingLimiterFromConfig(delegate, QueueLimiterConfig{ |
| 30 | + Ordering: OrderingFIFO, |
| 31 | + MaxBacklogSize: maxBacklogSize, |
| 32 | + MaxBacklogTimeout: maxBacklogTimeout, |
| 33 | + }), |
163 | 34 | }
|
164 | 35 | }
|
165 | 36 |
|
166 | 37 | // NewFifoBlockingLimiterWithDefaults will create a new FifoBlockingLimiter with default values.
|
| 38 | +// Deprecated, use NewQueueBlockingLimiterWithDefaults instead |
167 | 39 | func NewFifoBlockingLimiterWithDefaults(
|
168 | 40 | delegate core.Limiter,
|
169 | 41 | ) *FifoBlockingLimiter {
|
170 | 42 | return NewFifoBlockingLimiter(delegate, 100, time.Millisecond*1000)
|
171 | 43 | }
|
172 |
| - |
173 |
| -func (l *FifoBlockingLimiter) tryAcquire(ctx context.Context) core.Listener { |
174 |
| - // Try to acquire a token and return immediately if successful |
175 |
| - listener, ok := l.delegate.Acquire(ctx) |
176 |
| - if ok && listener != nil { |
177 |
| - return listener |
178 |
| - } |
179 |
| - |
180 |
| - // Restrict backlog size so the queue doesn't grow unbounded during an outage |
181 |
| - if l.backlog.len() >= l.maxBacklogSize { |
182 |
| - return nil |
183 |
| - } |
184 |
| - |
185 |
| - // Create a holder for a listener and block until a listener is released by another |
186 |
| - // operation. Holders will be unblocked in FIFO order |
187 |
| - event, eventReleaseChan := l.backlog.push(ctx) |
188 |
| - select { |
189 |
| - case listener = <-eventReleaseChan: |
190 |
| - return listener |
191 |
| - case <-time.After(l.maxBacklogTimeout): |
192 |
| - // Remove the holder from the backlog. This item is likely to be at the end of the |
193 |
| - // list so do a remove to minimize the number of items to traverse |
194 |
| - l.backlog.remove(event) |
195 |
| - return nil |
196 |
| - } |
197 |
| -} |
198 |
| - |
199 |
| -// Acquire a token from the limiter. Returns an Optional.empty() if the limit has been exceeded. |
200 |
| -// If acquired the caller must call one of the Listener methods when the operation has been completed to release |
201 |
| -// the count. |
202 |
| -// |
203 |
| -// context Context for the request. The context is used by advanced strategies such as LookupPartitionStrategy. |
204 |
| -func (l *FifoBlockingLimiter) Acquire(ctx context.Context) (core.Listener, bool) { |
205 |
| - delegateListener := l.tryAcquire(ctx) |
206 |
| - if delegateListener == nil { |
207 |
| - return nil, false |
208 |
| - } |
209 |
| - return &FifoBlockingListener{ |
210 |
| - delegateListener: delegateListener, |
211 |
| - limiter: l, |
212 |
| - }, true |
213 |
| -} |
214 |
| - |
215 |
| -func (l *FifoBlockingLimiter) String() string { |
216 |
| - return fmt.Sprintf("FifoBlockingLimiter{delegate=%v, maxBacklogSize=%d, maxBacklogTimeout=%v}", |
217 |
| - l.delegate, l.maxBacklogSize, l.maxBacklogTimeout) |
218 |
| -} |
0 commit comments