Skip to content

Commit

Permalink
Specify required RateLimiter permits on annotation (#2121)
Browse files Browse the repository at this point in the history
With this change you can specify the number of required permits for a
ratelimiter on the annotation level
  • Loading branch information
joshiste committed Apr 29, 2024
1 parent 01be4a1 commit eeaf57a
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,11 @@
* @return fallbackMethod method name.
*/
String fallbackMethod() default "";

/**
* Number of permits that this call requires.
*
* @return the number of permits that this call requires.
*/
int permits() default 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,19 @@ default void drainIfNeeded(Either<? extends Throwable, ?> callsResult) {
* @return the decorated CompletionStage.
*/
default <T> CompletionStage<T> executeCompletionStage(Supplier<CompletionStage<T>> supplier) {
return decorateCompletionStage(this, supplier).get();
return executeCompletionStage(1, supplier);
}

/**
* Decorates and executes the decorated CompletionStage.
*
* @param permits number of permits that this call requires
* @param supplier the original CompletionStage
* @param <T> the type of results supplied by this supplier
* @return the decorated CompletionStage.
*/
default <T> CompletionStage<T> executeCompletionStage(int permits, Supplier<CompletionStage<T>> supplier) {
return decorateCompletionStage(this, permits, supplier).get();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.github.resilience4j.common.ratelimiter.monitoring.endpoint.RateLimiterEndpointResponse;
import io.github.resilience4j.common.ratelimiter.monitoring.endpoint.RateLimiterEventDTO;
import io.github.resilience4j.common.ratelimiter.monitoring.endpoint.RateLimiterEventsEndpointResponse;
import io.github.resilience4j.consumer.EventConsumerRegistry;
import io.github.resilience4j.ratelimiter.autoconfigure.RateLimiterProperties;
import io.github.resilience4j.ratelimiter.configure.RateLimiterAspect;
import io.github.resilience4j.ratelimiter.event.RateLimiterEvent;
Expand Down Expand Up @@ -65,6 +66,8 @@ public class RateLimiterAutoConfigurationTest {
private TestRestTemplate restTemplate;
@Autowired
private RateLimiterDummyFeignClient rateLimiterDummyFeignClient;
@Autowired
private EventConsumerRegistry<RateLimiterEvent> eventConsumerRegistry;

/**
* This test verifies that the combination of @FeignClient and @RateLimiter annotation works as
Expand Down Expand Up @@ -208,4 +211,22 @@ public void testRateLimiterAutoConfiguration() throws IOException {
assertThat(backendCustomizer.getRateLimiterConfig().getLimitForPeriod()).isEqualTo(200);

}


@Test
public void testPermitsInRateLimiterAnnotation() {
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter(DummyService.BACKEND);
await()
.atMost(2, TimeUnit.SECONDS)
.until(() -> rateLimiter.getMetrics().getAvailablePermissions() == 10);

dummyService.doSomethingExpensive();
assertThat(rateLimiter.getMetrics().getAvailablePermissions()).isEqualTo(0);

assertThat(eventConsumerRegistry.getEventConsumer(DummyService.BACKEND).getBufferedEvents()).last()
.satisfies(event -> {
assertThat(event.getEventType()).isEqualTo(RateLimiterEvent.Type.SUCCESSFUL_ACQUIRE);
assertThat(event.getNumberOfPermits()).isEqualTo(10);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ public interface DummyService {
CompletableFuture<String> longDoSomethingAsync() throws InterruptedException;

CompletableFuture<String> doSomethingAsync(boolean throwException) throws IOException;

void doSomethingExpensive();

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@ public CompletableFuture<String> longDoSomethingAsync() {
Try.run(() -> Thread.sleep(2000));
return CompletableFuture.completedFuture("Test result");
}

@RateLimiter(name = BACKEND, permits = 10)
@Override
public void doSomethingExpensive() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ public Object rateLimiterAroundAdvice(ProceedingJoinPoint proceedingJoinPoint,
io.github.resilience4j.ratelimiter.RateLimiter rateLimiter = getOrCreateRateLimiter(
methodName, name);
Class<?> returnType = method.getReturnType();
final CheckedSupplier<Object> rateLimiterExecution = () -> proceed(proceedingJoinPoint, methodName, returnType, rateLimiter);
int permits = rateLimiterAnnotation.permits();
final CheckedSupplier<Object> rateLimiterExecution = () -> proceed(proceedingJoinPoint, methodName, returnType, rateLimiter, permits);
return fallbackExecutor.execute(proceedingJoinPoint, method, rateLimiterAnnotation.fallbackMethod(), rateLimiterExecution);
}

private Object proceed(ProceedingJoinPoint proceedingJoinPoint, String methodName,
Class<?> returnType, io.github.resilience4j.ratelimiter.RateLimiter rateLimiter)
Class<?> returnType, io.github.resilience4j.ratelimiter.RateLimiter rateLimiter, int permits)
throws Throwable {
if (rateLimiterAspectExtList != null && !rateLimiterAspectExtList.isEmpty()) {
for (RateLimiterAspectExt rateLimiterAspectExt : rateLimiterAspectExtList) {
Expand All @@ -128,9 +129,9 @@ private Object proceed(ProceedingJoinPoint proceedingJoinPoint, String methodNam
}
}
if (CompletionStage.class.isAssignableFrom(returnType)) {
return handleJoinPointCompletableFuture(proceedingJoinPoint, rateLimiter);
return handleJoinPointCompletableFuture(proceedingJoinPoint, rateLimiter, permits);
}
return handleJoinPoint(proceedingJoinPoint, rateLimiter);
return handleJoinPoint(proceedingJoinPoint, rateLimiter, permits);
}

private io.github.resilience4j.ratelimiter.RateLimiter getOrCreateRateLimiter(String methodName,
Expand Down Expand Up @@ -165,21 +166,22 @@ private RateLimiter getRateLimiterAnnotation(ProceedingJoinPoint proceedingJoinP
}

private Object handleJoinPoint(ProceedingJoinPoint proceedingJoinPoint,
io.github.resilience4j.ratelimiter.RateLimiter rateLimiter)
io.github.resilience4j.ratelimiter.RateLimiter rateLimiter, int permits)
throws Throwable {
return rateLimiter.executeCheckedSupplier(proceedingJoinPoint::proceed);
return rateLimiter.executeCheckedSupplier(permits, proceedingJoinPoint::proceed);
}

/**
* handle the asynchronous completable future flow
*
* @param proceedingJoinPoint AOPJoinPoint
* @param rateLimiter configured rate limiter
* @param permits
* @return CompletionStage
*/
private Object handleJoinPointCompletableFuture(ProceedingJoinPoint proceedingJoinPoint,
io.github.resilience4j.ratelimiter.RateLimiter rateLimiter) {
return rateLimiter.executeCompletionStage(() -> {
io.github.resilience4j.ratelimiter.RateLimiter rateLimiter, int permits) {
return rateLimiter.executeCompletionStage(permits, () -> {
try {
return (CompletionStage<?>) proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
Expand Down

0 comments on commit eeaf57a

Please sign in to comment.