3030import java .util .Optional ;
3131import java .util .concurrent .TimeUnit ;
3232import java .util .concurrent .atomic .AtomicInteger ;
33- import java .util .concurrent .locks .ReentrantLock ;
3433import java .util .function .Function ;
3534
3635public abstract class AbstractPartitionedLimiter <ContextT > extends AbstractLimiter <ContextT > {
@@ -105,10 +104,10 @@ public Limiter<ContextT> build() {
105104
106105 static class Partition {
107106 private final String name ;
107+ private final AtomicInteger busy = new AtomicInteger (0 );
108108
109109 private double percent = 0.0 ;
110- private int limit = 0 ;
111- private int busy = 0 ;
110+ private volatile int limit = 0 ;
112111 private long backoffMillis = 0 ;
113112 private MetricRegistry .SampleListener inflightDistribution ;
114113
@@ -134,25 +133,41 @@ void updateLimit(int totalLimit) {
134133 }
135134
136135 boolean isLimitExceeded () {
137- return busy >= limit ;
136+ return busy . get () >= limit ;
138137 }
139138
140139 void acquire () {
141- busy ++;
142- inflightDistribution .addSample (busy );
140+ int nowBusy = busy .incrementAndGet ();
141+ inflightDistribution .addSample (nowBusy );
142+ }
143+
144+ /**
145+ * Try to acquire a slot, returning false if the limit is exceeded.
146+ * @return
147+ */
148+ boolean tryAcquire () {
149+ int current = busy .get ();
150+ while (current < limit ) {
151+ if (busy .compareAndSet (current , current + 1 )) {
152+ inflightDistribution .addSample (current + 1 );
153+ return true ;
154+ }
155+ current = busy .get ();
156+ }
143157
158+ return false ;
144159 }
145160
146161 void release () {
147- busy -- ;
162+ busy . decrementAndGet () ;
148163 }
149164
150165 int getLimit () {
151166 return limit ;
152167 }
153168
154169 public int getInflight () {
155- return busy ;
170+ return busy . get () ;
156171 }
157172
158173 double getPercent () {
@@ -166,14 +181,13 @@ void createMetrics(MetricRegistry registry) {
166181
167182 @ Override
168183 public String toString () {
169- return "Partition [pct=" + percent + ", limit=" + limit + ", busy=" + busy + "]" ;
184+ return "Partition [pct=" + percent + ", limit=" + limit + ", busy=" + busy . get () + "]" ;
170185 }
171186 }
172187
173188 private final Map <String , Partition > partitions ;
174189 private final Partition unknownPartition ;
175190 private final List <Function <ContextT , String >> partitionResolvers ;
176- private final ReentrantLock lock = new ReentrantLock ();
177191 private final AtomicInteger delayedThreads = new AtomicInteger ();
178192 private final int maxDelayedThreads ;
179193
@@ -211,63 +225,67 @@ private Partition resolvePartition(ContextT context) {
211225
212226 @ Override
213227 public Optional <Listener > acquire (ContextT context ) {
214- final Partition partition = resolvePartition (context );
215-
216- try {
217- lock .lock ();
218- if (shouldBypass (context )){
219- return createBypassListener ();
220- }
221- if (getInflight () >= getLimit () && partition .isLimitExceeded ()) {
222- lock .unlock ();
223- if (partition .backoffMillis > 0 && delayedThreads .get () < maxDelayedThreads ) {
224- try {
225- delayedThreads .incrementAndGet ();
226- TimeUnit .MILLISECONDS .sleep (partition .backoffMillis );
227- } catch (InterruptedException e ) {
228- Thread .currentThread ().interrupt ();
229- } finally {
230- delayedThreads .decrementAndGet ();
231- }
232- }
228+ if (shouldBypass (context )){
229+ return createBypassListener ();
230+ }
233231
234- return createRejectedListener ();
235- }
232+ final Partition partition = resolvePartition (context );
236233
234+ // This is a little unusual in that the partition is not a hard limit. It is
235+ // only a limit that it is applied if the global limit is exceeded. This allows
236+ // for excess capacity in each partition to allow for bursting over the limit,
237+ // but only if there is spare global capacity.
238+
239+ final boolean overLimit ;
240+ if (getInflight () >= getLimit ()) {
241+ // over global limit, so respect partition limit
242+ boolean couldAcquire = partition .tryAcquire ();
243+ overLimit = !couldAcquire ;
244+ } else {
245+ // we are below global limit, so no need to respect partition limit
237246 partition .acquire ();
238- final Listener listener = createListener ();
239- return Optional .of (new Listener () {
240- @ Override
241- public void onSuccess () {
242- listener .onSuccess ();
243- releasePartition (partition );
244- }
247+ overLimit = false ;
248+ }
245249
246- @ Override
247- public void onIgnore () {
248- listener .onIgnore ();
249- releasePartition (partition );
250+ if (overLimit ) {
251+ if (partition .backoffMillis > 0 && delayedThreads .get () < maxDelayedThreads ) {
252+ try {
253+ delayedThreads .incrementAndGet ();
254+ TimeUnit .MILLISECONDS .sleep (partition .backoffMillis );
255+ } catch (InterruptedException e ) {
256+ Thread .currentThread ().interrupt ();
257+ } finally {
258+ delayedThreads .decrementAndGet ();
250259 }
260+ }
251261
252- @ Override
253- public void onDropped () {
254- listener .onDropped ();
255- releasePartition (partition );
256- }
257- });
258- } finally {
259- if (lock .isHeldByCurrentThread ())
260- lock .unlock ();
262+ return createRejectedListener ();
261263 }
264+
265+ final Listener listener = createListener ();
266+ return Optional .of (new Listener () {
267+ @ Override
268+ public void onSuccess () {
269+ listener .onSuccess ();
270+ releasePartition (partition );
271+ }
272+
273+ @ Override
274+ public void onIgnore () {
275+ listener .onIgnore ();
276+ releasePartition (partition );
277+ }
278+
279+ @ Override
280+ public void onDropped () {
281+ listener .onDropped ();
282+ releasePartition (partition );
283+ }
284+ });
262285 }
263286
264287 private void releasePartition (Partition partition ) {
265- try {
266- lock .lock ();
267- partition .release ();
268- } finally {
269- lock .unlock ();
270- }
288+ partition .release ();
271289 }
272290
273291 @ Override
0 commit comments