Skip to content

Commit fb143f3

Browse files
authored
Reactor and RxJava RateLimiter operator try to reserve a permission and d… (ReactiveX#465)
* Reactor and RxJava RateLimiter operator try to reserve a permission and delay the subscription if necessary.
1 parent 44c6af9 commit fb143f3

File tree

23 files changed

+288
-151
lines changed

23 files changed

+288
-151
lines changed

resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiter.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,7 @@ static <T, R> Function<T, R> decorateFunction(RateLimiter rateLimiter, Function<
239239
* @throws IllegalStateException if thread was interrupted during permission wait
240240
*/
241241
static void waitForPermission(final RateLimiter rateLimiter) {
242-
RateLimiterConfig rateLimiterConfig = rateLimiter.getRateLimiterConfig();
243-
Duration timeoutDuration = rateLimiterConfig.getTimeoutDuration();
244-
boolean permission = rateLimiter.acquirePermission(timeoutDuration);
242+
boolean permission = rateLimiter.acquirePermission();
245243
if (Thread.interrupted()) {
246244
throw new IllegalStateException("Thread was interrupted during permission wait");
247245
}
@@ -275,28 +273,41 @@ static void waitForPermission(final RateLimiter rateLimiter) {
275273
@Deprecated
276274
boolean getPermission(Duration timeoutDuration);
277275

276+
/**
277+
* @deprecated Use {@link RateLimiter#acquirePermission()} instead.
278+
* @since 0.16.0
279+
*/
280+
@Deprecated
281+
boolean acquirePermission(Duration timeoutDuration);
282+
278283
/**
279284
* Acquires a permission from this rate limiter, blocking until one is available, or the thread is interrupted.
285+
* Maximum wait time is {@link RateLimiterConfig#getTimeoutDuration()}
280286
*
281287
* <p>If the current thread is {@linkplain Thread#interrupt interrupted}
282288
* while waiting for a permit then it won't throw {@linkplain InterruptedException},
283289
* but its interrupt status will be set.
284290
*
285-
* @param timeoutDuration the maximum time to wait
286291
* @return {@code true} if a permit was acquired and {@code false}
287292
* if waiting timeoutDuration elapsed before a permit was acquired
288293
*/
289-
boolean acquirePermission(Duration timeoutDuration);
294+
boolean acquirePermission();
295+
296+
/**
297+
* @deprecated Use {@link RateLimiter#reservePermission()} instead.
298+
* @since 0.16.0
299+
*/
300+
@Deprecated
301+
long reservePermission(Duration timeoutDuration);
290302

291303
/**
292304
* Reserves a permission from this rate limiter and returns nanoseconds you should wait for it.
293305
* If returned long is negative, it means that you failed to reserve permission,
294-
* possibly your {@code timeoutDuration} is less then time to wait for permission.
306+
* possibly your {@link RateLimiterConfig#getTimeoutDuration()} is less then time to wait for permission.
295307
*
296-
* @param timeoutDuration the maximum time you want to wait.
297308
* @return {@code long} amount of nanoseconds you should wait for reserved permission. if negative, it means you failed to reserve.
298309
*/
299-
long reservePermission(Duration timeoutDuration);
310+
long reservePermission();
300311

301312
/**
302313
* Get the name of this RateLimiter

resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/AtomicRateLimiter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public class AtomicRateLimiter implements RateLimiter {
5151
private final AtomicReference<State> state;
5252
private final RateLimiterEventProcessor eventProcessor;
5353

54-
5554
public AtomicRateLimiter(String name, RateLimiterConfig rateLimiterConfig) {
5655
this.name = name;
5756

@@ -112,6 +111,11 @@ public boolean acquirePermission(Duration timeoutDuration) {
112111
return result;
113112
}
114113

114+
@Override
115+
public boolean acquirePermission() {
116+
return acquirePermission(state.get().config.getTimeoutDuration());
117+
}
118+
115119
/**
116120
* {@inheritDoc}
117121
*/
@@ -136,6 +140,11 @@ public long reservePermission(Duration timeoutDuration) {
136140
return -1;
137141
}
138142

143+
@Override
144+
public long reservePermission() {
145+
return reservePermission(state.get().config.getTimeoutDuration());
146+
}
147+
139148
/**
140149
* Atomically updates the current {@link State} with the results of
141150
* applying the {@link AtomicRateLimiter#calculateNextState}, returning the updated {@link State}.

resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/SemaphoreBasedRateLimiter.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737

3838
/**
3939
* A RateLimiter implementation that consists of {@link Semaphore}
40-
* and scheduler that will refresh permissions
41-
* after each {@link RateLimiterConfig#limitRefreshPeriod}.
40+
* and scheduler that will refresh permissions after each {@link RateLimiterConfig#getLimitRefreshPeriod()}.
4241
*/
4342
public class SemaphoreBasedRateLimiter implements RateLimiter {
4443

@@ -149,6 +148,11 @@ public boolean acquirePermission(Duration timeoutDuration) {
149148
}
150149
}
151150

151+
@Override
152+
public boolean acquirePermission() {
153+
return acquirePermission(rateLimiterConfig.get().getTimeoutDuration());
154+
}
155+
152156
/**
153157
* {@inheritDoc}
154158
* SemaphoreBasedRateLimiter is totally blocking by it's nature. So this non-blocking API isn't supported.
@@ -159,6 +163,16 @@ public long reservePermission(Duration timeoutDuration) {
159163
return -1;
160164
}
161165

166+
/**
167+
* {@inheritDoc}
168+
* SemaphoreBasedRateLimiter is totally blocking by it's nature. So this non-blocking API isn't supported.
169+
* It will return negative numbers all the time.
170+
*/
171+
@Override
172+
public long reservePermission() {
173+
return -1;
174+
}
175+
162176
/**
163177
* {@inheritDoc}
164178
*/

resilience4j-ratelimiter/src/test/java/io/github/resilience4j/ratelimiter/RateLimiterTest.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ public void decorateCheckedSupplier() throws Throwable {
7575
CheckedFunction0 supplier = mock(CheckedFunction0.class);
7676
CheckedFunction0 decorated = RateLimiter.decorateCheckedSupplier(limit, supplier);
7777

78-
when(limit.acquirePermission(config.getTimeoutDuration()))
78+
when(limit.acquirePermission())
7979
.thenReturn(false);
8080

8181
Try decoratedSupplierResult = Try.of(decorated);
8282
then(decoratedSupplierResult.isFailure()).isTrue();
8383
then(decoratedSupplierResult.getCause()).isInstanceOf(RequestNotPermitted.class);
8484
verify(supplier, never()).apply();
8585

86-
when(limit.acquirePermission(config.getTimeoutDuration()))
86+
when(limit.acquirePermission())
8787
.thenReturn(true);
8888
Try secondSupplierResult = Try.of(decorated);
8989
then(secondSupplierResult.isSuccess()).isTrue();
@@ -95,15 +95,15 @@ public void decorateCheckedRunnable() throws Throwable {
9595
CheckedRunnable runnable = mock(CheckedRunnable.class);
9696
CheckedRunnable decorated = RateLimiter.decorateCheckedRunnable(limit, runnable);
9797

98-
when(limit.acquirePermission(config.getTimeoutDuration()))
98+
when(limit.acquirePermission())
9999
.thenReturn(false);
100100

101101
Try decoratedRunnableResult = Try.run(decorated);
102102
then(decoratedRunnableResult.isFailure()).isTrue();
103103
then(decoratedRunnableResult.getCause()).isInstanceOf(RequestNotPermitted.class);
104104
verify(runnable, never()).run();
105105

106-
when(limit.acquirePermission(config.getTimeoutDuration()))
106+
when(limit.acquirePermission())
107107
.thenReturn(true);
108108
Try secondRunnableResult = Try.run(decorated);
109109
then(secondRunnableResult.isSuccess()).isTrue();
@@ -115,15 +115,15 @@ public void decorateCheckedFunction() throws Throwable {
115115
CheckedFunction1<Integer, String> function = mock(CheckedFunction1.class);
116116
CheckedFunction1<Integer, String> decorated = RateLimiter.decorateCheckedFunction(limit, function);
117117

118-
when(limit.acquirePermission(config.getTimeoutDuration()))
118+
when(limit.acquirePermission())
119119
.thenReturn(false);
120120

121121
Try<String> decoratedFunctionResult = Try.success(1).mapTry(decorated);
122122
then(decoratedFunctionResult.isFailure()).isTrue();
123123
then(decoratedFunctionResult.getCause()).isInstanceOf(RequestNotPermitted.class);
124124
verify(function, never()).apply(any());
125125

126-
when(limit.acquirePermission(config.getTimeoutDuration()))
126+
when(limit.acquirePermission())
127127
.thenReturn(true);
128128
Try secondFunctionResult = Try.success(1).mapTry(decorated);
129129
then(secondFunctionResult.isSuccess()).isTrue();
@@ -135,15 +135,15 @@ public void decorateSupplier() throws Exception {
135135
Supplier supplier = mock(Supplier.class);
136136
Supplier decorated = RateLimiter.decorateSupplier(limit, supplier);
137137

138-
when(limit.acquirePermission(config.getTimeoutDuration()))
138+
when(limit.acquirePermission())
139139
.thenReturn(false);
140140

141141
Try decoratedSupplierResult = Try.success(decorated).map(Supplier::get);
142142
then(decoratedSupplierResult.isFailure()).isTrue();
143143
then(decoratedSupplierResult.getCause()).isInstanceOf(RequestNotPermitted.class);
144144
verify(supplier, never()).get();
145145

146-
when(limit.acquirePermission(config.getTimeoutDuration()))
146+
when(limit.acquirePermission())
147147
.thenReturn(true);
148148
Try secondSupplierResult = Try.success(decorated).map(Supplier::get);
149149
then(secondSupplierResult.isSuccess()).isTrue();
@@ -155,15 +155,15 @@ public void decorateConsumer() throws Exception {
155155
Consumer<Integer> consumer = mock(Consumer.class);
156156
Consumer<Integer> decorated = RateLimiter.decorateConsumer(limit, consumer);
157157

158-
when(limit.acquirePermission(config.getTimeoutDuration()))
158+
when(limit.acquirePermission())
159159
.thenReturn(false);
160160

161161
Try<Integer> decoratedConsumerResult = Try.success(1).andThen(decorated);
162162
then(decoratedConsumerResult.isFailure()).isTrue();
163163
then(decoratedConsumerResult.getCause()).isInstanceOf(RequestNotPermitted.class);
164164
verify(consumer, never()).accept(any());
165165

166-
when(limit.acquirePermission(config.getTimeoutDuration()))
166+
when(limit.acquirePermission())
167167
.thenReturn(true);
168168
Try secondConsumerResult = Try.success(1).andThen(decorated);
169169
then(secondConsumerResult.isSuccess()).isTrue();
@@ -175,15 +175,15 @@ public void decorateRunnable() throws Exception {
175175
Runnable runnable = mock(Runnable.class);
176176
Runnable decorated = RateLimiter.decorateRunnable(limit, runnable);
177177

178-
when(limit.acquirePermission(config.getTimeoutDuration()))
178+
when(limit.acquirePermission())
179179
.thenReturn(false);
180180

181181
Try decoratedRunnableResult = Try.success(decorated).andThen(Runnable::run);
182182
then(decoratedRunnableResult.isFailure()).isTrue();
183183
then(decoratedRunnableResult.getCause()).isInstanceOf(RequestNotPermitted.class);
184184
verify(runnable, never()).run();
185185

186-
when(limit.acquirePermission(config.getTimeoutDuration()))
186+
when(limit.acquirePermission())
187187
.thenReturn(true);
188188
Try secondRunnableResult = Try.success(decorated).andThen(Runnable::run);
189189
then(secondRunnableResult.isSuccess()).isTrue();
@@ -195,15 +195,15 @@ public void decorateFunction() throws Exception {
195195
Function<Integer, String> function = mock(Function.class);
196196
Function<Integer, String> decorated = RateLimiter.decorateFunction(limit, function);
197197

198-
when(limit.acquirePermission(config.getTimeoutDuration()))
198+
when(limit.acquirePermission())
199199
.thenReturn(false);
200200

201201
Try<String> decoratedFunctionResult = Try.success(1).map(decorated);
202202
then(decoratedFunctionResult.isFailure()).isTrue();
203203
then(decoratedFunctionResult.getCause()).isInstanceOf(RequestNotPermitted.class);
204204
verify(function, never()).apply(any());
205205

206-
when(limit.acquirePermission(config.getTimeoutDuration()))
206+
when(limit.acquirePermission())
207207
.thenReturn(true);
208208
Try secondFunctionResult = Try.success(1).map(decorated);
209209
then(secondFunctionResult.isSuccess()).isTrue();
@@ -218,7 +218,7 @@ public void decorateCompletionStage() throws Exception {
218218

219219
Supplier<CompletionStage<String>> decorated = RateLimiter.decorateCompletionStage(limit, completionStage);
220220

221-
when(limit.acquirePermission(config.getTimeoutDuration()))
221+
when(limit.acquirePermission())
222222
.thenReturn(false);
223223

224224
AtomicReference<Throwable> error = new AtomicReference<>(null);
@@ -232,7 +232,7 @@ public void decorateCompletionStage() throws Exception {
232232
then(error.get()).isExactlyInstanceOf(RequestNotPermitted.class);
233233
verify(supplier, never()).get();
234234

235-
when(limit.acquirePermission(config.getTimeoutDuration()))
235+
when(limit.acquirePermission())
236236
.thenReturn(true);
237237

238238
AtomicReference<Throwable> shouldBeEmpty = new AtomicReference<>(null);
@@ -248,25 +248,25 @@ public void decorateCompletionStage() throws Exception {
248248

249249
@Test
250250
public void waitForPermissionWithOne() throws Exception {
251-
when(limit.acquirePermission(config.getTimeoutDuration()))
251+
when(limit.acquirePermission())
252252
.thenReturn(true);
253253
RateLimiter.waitForPermission(limit);
254254
verify(limit, times(1))
255-
.acquirePermission(config.getTimeoutDuration());
255+
.acquirePermission();
256256
}
257257

258258
@Test(expected = RequestNotPermitted.class)
259259
public void waitForPermissionWithoutOne() throws Exception {
260-
when(limit.acquirePermission(config.getTimeoutDuration()))
260+
when(limit.acquirePermission())
261261
.thenReturn(false);
262262
RateLimiter.waitForPermission(limit);
263263
verify(limit, times(1))
264-
.acquirePermission(config.getTimeoutDuration());
264+
.acquirePermission();
265265
}
266266

267267
@Test
268268
public void waitForPermissionWithInterruption() throws Exception {
269-
when(limit.acquirePermission(config.getTimeoutDuration()))
269+
when(limit.acquirePermission())
270270
.then(invocation -> {
271271
LockSupport.parkNanos(5_000_000_000L);
272272
return null;

0 commit comments

Comments
 (0)