11
11
import java .time .Duration ;
12
12
import java .util .concurrent .atomic .AtomicInteger ;
13
13
import java .util .concurrent .atomic .AtomicReference ;
14
+ import java .util .function .UnaryOperator ;
14
15
15
16
/**
16
17
* {@link AtomicRateLimiter} splits all nanoseconds from the start of epoch into cycles.
@@ -31,7 +32,7 @@ public class AtomicRateLimiter implements RateLimiter {
31
32
private final long cyclePeriodInNanos ;
32
33
private final int permissionsPerCycle ;
33
34
private final AtomicInteger waitingThreads ;
34
- public final AtomicReference <State > state ;
35
+ private final AtomicReference <State > state ;
35
36
36
37
37
38
public AtomicRateLimiter (String name , RateLimiterConfig rateLimiterConfig ) {
@@ -51,12 +52,57 @@ public AtomicRateLimiter(String name, RateLimiterConfig rateLimiterConfig) {
51
52
@ Override
52
53
public boolean getPermission (final Duration timeoutDuration ) {
53
54
long timeoutInNanos = timeoutDuration .toNanos ();
54
- State modifiedState = state .updateAndGet (
55
- activeState -> calculateNextState (timeoutInNanos , activeState )
56
- );
55
+ State modifiedState = updateStateWithBackOff (timeoutInNanos );
57
56
return waitForPermissionIfNecessary (timeoutInNanos , modifiedState .nanosToWait );
58
57
}
59
58
59
+ /**
60
+ * Atomically updates the current {@link State} with the results of
61
+ * applying the {@link AtomicRateLimiter#calculateNextState}, returning the updated {@link State}.
62
+ * It differs from {@link AtomicReference#updateAndGet(UnaryOperator)} by constant back off.
63
+ * It means that after one try to {@link AtomicReference#compareAndSet(Object, Object)}
64
+ * this method will wait for a while before try one more time.
65
+ * This technique was originally described in this
66
+ * <a href="https://arxiv.org/abs/1305.5800"> paper</a>
67
+ * and showed great results with {@link AtomicRateLimiter} in benchmark tests.
68
+ *
69
+ * @param timeoutInNanos a side-effect-free function
70
+ * @return the updated value
71
+ */
72
+ private State updateStateWithBackOff (final long timeoutInNanos ) {
73
+ AtomicRateLimiter .State prev ;
74
+ AtomicRateLimiter .State next ;
75
+ do {
76
+ prev = state .get ();
77
+ next = calculateNextState (timeoutInNanos , prev );
78
+ } while (!compareAndSet (prev , next ));
79
+ return next ;
80
+ }
81
+
82
+ /**
83
+ * Atomically sets the value to the given updated value
84
+ * if the current value {@code ==} the expected value.
85
+ * It differs from {@link AtomicReference#updateAndGet(UnaryOperator)} by constant back off.
86
+ * It means that after one try to {@link AtomicReference#compareAndSet(Object, Object)}
87
+ * this method will wait for a while before try one more time.
88
+ * This technique was originally described in this
89
+ * <a href="https://arxiv.org/abs/1305.5800"> paper</a>
90
+ * and showed great results with {@link AtomicRateLimiter} in benchmark tests.
91
+ *
92
+ * @param current the expected value
93
+ * @param next the new value
94
+ * @return {@code true} if successful. False return indicates that
95
+ * the actual value was not equal to the expected value.
96
+ */
97
+ private boolean compareAndSet (final State current , final State next ) {
98
+ if (state .compareAndSet (current , next )) {
99
+ return true ;
100
+ } else {
101
+ parkNanos (1 ); // back-off
102
+ return false ;
103
+ }
104
+ }
105
+
60
106
/**
61
107
* A side-effect-free function that can calculate next {@link State} from current.
62
108
* It determines time duration that you should wait for permission and reserves it for you,
@@ -66,7 +112,7 @@ public boolean getPermission(final Duration timeoutDuration) {
66
112
* @param activeState current state of {@link AtomicRateLimiter}
67
113
* @return next {@link State}
68
114
*/
69
- public State calculateNextState (final long timeoutInNanos , final State activeState ) {
115
+ private State calculateNextState (final long timeoutInNanos , final State activeState ) {
70
116
long currentNanos = currentNanoTime ();
71
117
long currentCycle = currentNanos / cyclePeriodInNanos ;
72
118
@@ -93,7 +139,7 @@ public State calculateNextState(final long timeoutInNanos, final State activeSta
93
139
* @param currentCycle current {@link AtomicRateLimiter} cycle
94
140
* @return nanoseconds to wait for the next permission
95
141
*/
96
- public long nanosToWaitForPermission (final int availablePermissions , final long currentNanos , final long currentCycle ) {
142
+ private long nanosToWaitForPermission (final int availablePermissions , final long currentNanos , final long currentCycle ) {
97
143
if (availablePermissions > 0 ) {
98
144
return 0L ;
99
145
} else {
@@ -114,7 +160,7 @@ public long nanosToWaitForPermission(final int availablePermissions, final long
114
160
* @param nanosToWait nanoseconds to wait for the next permission
115
161
* @return new {@link State} with possibly reserved permissions and time to wait
116
162
*/
117
- public State reservePermissions (final long timeoutInNanos , final long cycle , final int permissions , final long nanosToWait ) {
163
+ private State reservePermissions (final long timeoutInNanos , final long cycle , final int permissions , final long nanosToWait ) {
118
164
boolean canAcquireInTime = timeoutInNanos >= nanosToWait ;
119
165
int permissionsWithReservation = permissions ;
120
166
if (canAcquireInTime ) {
@@ -130,7 +176,7 @@ public State reservePermissions(final long timeoutInNanos, final long cycle, fin
130
176
* @param nanosToWait nanoseconds caller need to wait
131
177
* @return true if caller was able to wait for nanosToWait without {@link Thread#interrupt} and not exceed timeout
132
178
*/
133
- public boolean waitForPermissionIfNecessary (final long timeoutInNanos , final long nanosToWait ) {
179
+ private boolean waitForPermissionIfNecessary (final long timeoutInNanos , final long nanosToWait ) {
134
180
boolean canAcquireImmediately = nanosToWait <= 0 ;
135
181
boolean canAcquireInTime = timeoutInNanos >= nanosToWait ;
136
182
@@ -153,7 +199,7 @@ public boolean waitForPermissionIfNecessary(final long timeoutInNanos, final lon
153
199
* @param nanosToWait nanoseconds caller need to wait
154
200
* @return true if caller was not {@link Thread#interrupted} while waiting
155
201
*/
156
- public boolean waitForPermission (final long nanosToWait ) {
202
+ private boolean waitForPermission (final long nanosToWait ) {
157
203
waitingThreads .incrementAndGet ();
158
204
long deadline = currentNanoTime () + nanosToWait ;
159
205
boolean wasInterrupted = false ;
@@ -207,13 +253,14 @@ public AtomicRateLimiterMetrics getMetrics() {
207
253
* the last {@link AtomicRateLimiter#getPermission(Duration)} call.</li>
208
254
* </ul>
209
255
*/
210
- public static class State {
256
+ private static class State {
211
257
212
258
private final long activeCycle ;
259
+
213
260
private final int activePermissions ;
214
261
private final long nanosToWait ;
215
262
216
- public State (final long activeCycle , final int activePermissions , final long nanosToWait ) {
263
+ private State (final long activeCycle , final int activePermissions , final long nanosToWait ) {
217
264
this .activeCycle = activeCycle ;
218
265
this .activePermissions = activePermissions ;
219
266
this .nanosToWait = nanosToWait ;
@@ -259,12 +306,13 @@ public long getAvailablePermissions() {
259
306
State estimatedState = calculateNextState (-1 , currentState );
260
307
return estimatedState .activePermissions ;
261
308
}
309
+
262
310
}
263
311
264
312
/**
265
313
* Created only for test purposes. Simply calls {@link System#nanoTime()}
266
314
*/
267
- public long currentNanoTime () {
315
+ private long currentNanoTime () {
268
316
return nanoTime ();
269
317
}
270
318
}
0 commit comments