Skip to content

Commit 322ee78

Browse files
authored
Issue ReactiveX#518: Added decorators to CircuitBreaker and Retry for Supplier… (ReactiveX#519)
1 parent fa85386 commit 322ee78

File tree

10 files changed

+771
-19
lines changed

10 files changed

+771
-19
lines changed

resilience4j-bulkhead/src/main/java/io/github/resilience4j/bulkhead/Bulkhead.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import io.vavr.CheckedFunction0;
2929
import io.vavr.CheckedFunction1;
3030
import io.vavr.CheckedRunnable;
31+
import io.vavr.control.Either;
32+
import io.vavr.control.Try;
3133

3234
import java.util.concurrent.Callable;
3335
import java.util.concurrent.CompletableFuture;
@@ -127,6 +129,28 @@ default <T> T executeSupplier(Supplier<T> supplier){
127129
return decorateSupplier(this, supplier).get();
128130
}
129131

132+
/**
133+
* Decorates and executes the decorated Supplier.
134+
*
135+
* @param supplier the original Supplier
136+
* @param <T> the type of results supplied by this supplier
137+
* @return the result of the decorated Supplier.
138+
*/
139+
default <T> Try<T> executeTrySupplier(Supplier<Try<T>> supplier){
140+
return decorateTrySupplier(this, supplier).get();
141+
}
142+
143+
/**
144+
* Decorates and executes the decorated Supplier.
145+
*
146+
* @param supplier the original Supplier
147+
* @param <T> the type of results supplied by this supplier
148+
* @return the result of the decorated Supplier.
149+
*/
150+
default <T> Either<Exception, T> executeEitherSupplier(Supplier<Either<? extends Exception, T>> supplier){
151+
return decorateEitherSupplier(this, supplier).get();
152+
}
153+
130154
/**
131155
* Decorates and executes the decorated Callable.
132156
*
@@ -294,6 +318,55 @@ static <T> Supplier<T> decorateSupplier(Bulkhead bulkhead, Supplier<T> supplier)
294318
};
295319
}
296320

321+
/**
322+
* Returns a supplier which is decorated by a bulkhead.
323+
*
324+
* @param bulkhead the bulkhead
325+
* @param supplier the original supplier
326+
* @param <T> the type of results supplied by this supplier
327+
*
328+
* @return a supplier which is decorated by a Bulkhead.
329+
*/
330+
static <T> Supplier<Try<T>> decorateTrySupplier(Bulkhead bulkhead, Supplier<Try<T>> supplier){
331+
return () -> {
332+
if(bulkhead.tryAcquirePermission()){
333+
try {
334+
return supplier.get();
335+
}
336+
finally {
337+
bulkhead.onComplete();
338+
}
339+
}else{
340+
return Try.failure(new BulkheadFullException(bulkhead));
341+
}
342+
};
343+
}
344+
345+
/**
346+
* Returns a supplier which is decorated by a bulkhead.
347+
*
348+
* @param bulkhead the bulkhead
349+
* @param supplier the original supplier
350+
* @param <T> the type of results supplied by this supplier
351+
*
352+
* @return a supplier which is decorated by a Bulkhead.
353+
*/
354+
static <T> Supplier<Either<Exception, T>> decorateEitherSupplier(Bulkhead bulkhead, Supplier<Either<? extends Exception, T>> supplier){
355+
return () -> {
356+
if(bulkhead.tryAcquirePermission()){
357+
try {
358+
Either<? extends Exception, T> result = supplier.get();
359+
return Either.narrow(result);
360+
}
361+
finally {
362+
bulkhead.onComplete();
363+
}
364+
}else{
365+
return Either.left(new BulkheadFullException(bulkhead));
366+
}
367+
};
368+
}
369+
297370
/**
298371
* Returns a consumer which is decorated by a bulkhead.
299372

resilience4j-bulkhead/src/test/java/io/github/resilience4j/bulkhead/BulkheadTest.java

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.vavr.CheckedFunction0;
2424
import io.vavr.CheckedFunction1;
2525
import io.vavr.CheckedRunnable;
26+
import io.vavr.control.Either;
2627
import io.vavr.control.Try;
2728
import org.junit.Before;
2829
import org.junit.Test;
@@ -40,6 +41,7 @@
4041

4142
import static org.assertj.core.api.Assertions.assertThat;
4243
import static org.assertj.core.api.Assertions.assertThatThrownBy;
44+
import static org.mockito.Mockito.never;
4345
import static org.mockito.Mockito.times;
4446

4547
public class BulkheadTest {
@@ -595,4 +597,98 @@ public void shouldInvokeMap() {
595597
// end::shouldInvokeMap[]
596598
}
597599

600+
@Test
601+
public void shouldDecorateTrySupplierAndReturnWithSuccess() {
602+
// Given
603+
Bulkhead bulkhead = Bulkhead.of("test", config);
604+
BDDMockito.given(helloWorldService.returnTry()).willReturn(Try.success("Hello world"));
605+
606+
// When
607+
Try<String> result = bulkhead.executeTrySupplier(helloWorldService::returnTry);
608+
609+
// Then
610+
assertThat(result.get()).isEqualTo("Hello world");
611+
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);
612+
BDDMockito.then(helloWorldService).should(times(1)).returnTry();
613+
}
614+
615+
@Test
616+
public void shouldDecorateTrySupplierAndReturnWithException() {
617+
// Given
618+
Bulkhead bulkhead = Bulkhead.of("test", config);
619+
BDDMockito.given(helloWorldService.returnTry()).willReturn(Try.failure(new RuntimeException("BAM!")));
620+
621+
// When
622+
Try<String> result = bulkhead.executeTrySupplier(helloWorldService::returnTry);
623+
624+
//Then
625+
assertThat(result.isFailure()).isTrue();
626+
assertThat(result.failed().get()).isInstanceOf(RuntimeException.class);
627+
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);
628+
BDDMockito.then(helloWorldService).should(times(1)).returnTry();
629+
}
630+
631+
@Test
632+
public void shouldDecorateEitherSupplierAndReturnWithSuccess() {
633+
634+
// Given
635+
Bulkhead bulkhead = Bulkhead.of("test", config);
636+
BDDMockito.given(helloWorldService.returnEither()).willReturn(Either.right("Hello world"));
637+
638+
// When
639+
Either<Exception, String> result = bulkhead.executeEitherSupplier(helloWorldService::returnEither);
640+
641+
// Then
642+
assertThat(result.get()).isEqualTo("Hello world");
643+
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);
644+
BDDMockito.then(helloWorldService).should(times(1)).returnEither();
645+
}
646+
647+
@Test
648+
public void shouldDecorateEitherSupplierAndReturnWithException() {
649+
// Given
650+
Bulkhead bulkhead = Bulkhead.of("test", config);
651+
BDDMockito.given(helloWorldService.returnEither()).willReturn(Either.left(new WebServiceException("BAM!")));
652+
653+
// When
654+
Either<Exception, String> result = bulkhead.executeEitherSupplier(helloWorldService::returnEither);
655+
656+
//Then
657+
assertThat(result.isLeft()).isTrue();
658+
assertThat(result.getLeft()).isInstanceOf(RuntimeException.class);
659+
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);
660+
BDDMockito.then(helloWorldService).should(times(1)).returnEither();
661+
}
662+
663+
@Test
664+
public void shouldDecorateTrySupplierAndReturnWithBulkheadFullException() {
665+
// Given
666+
Bulkhead bulkhead = Mockito.mock(Bulkhead.class);
667+
BDDMockito.given(bulkhead.tryAcquirePermission()).willReturn(false);
668+
669+
// When
670+
Try<String> result = Bulkhead.decorateTrySupplier(bulkhead, helloWorldService::returnTry).get();
671+
672+
//Then
673+
assertThat(result.isFailure()).isTrue();
674+
assertThat(result.getCause()).isInstanceOf(BulkheadFullException.class);
675+
BDDMockito.then(helloWorldService).should(never()).returnTry();
676+
}
677+
678+
@Test
679+
public void shouldDecorateEitherSupplierAndReturnWithBulkheadFullException() {
680+
// Given
681+
Bulkhead bulkhead = Mockito.mock(Bulkhead.class);
682+
BDDMockito.given(bulkhead.tryAcquirePermission()).willReturn(false);
683+
684+
// When
685+
Either<Exception, String> result = Bulkhead.decorateEitherSupplier(bulkhead, helloWorldService::returnEither).get();
686+
687+
//Then
688+
assertThat(result.isLeft()).isTrue();
689+
assertThat(result.getLeft()).isInstanceOf(BulkheadFullException.class);
690+
BDDMockito.then(helloWorldService).should(never()).returnEither();
691+
}
692+
693+
598694
}

resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreaker.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine;
2323
import io.github.resilience4j.core.EventConsumer;
2424
import io.vavr.*;
25+
import io.vavr.control.Either;
26+
import io.vavr.control.Try;
2527

2628
import java.util.Arrays;
2729
import java.util.Map;
@@ -210,6 +212,52 @@ default <T> Supplier<T> decorateSupplier(Supplier<T> supplier){
210212
return decorateSupplier(this, supplier);
211213
}
212214

215+
/**
216+
* Decorates and executes the decorated Supplier.
217+
*
218+
* @param supplier the original Supplier
219+
* @param <T> the type of results supplied by this supplier
220+
* @return the result of the decorated Supplier.
221+
*/
222+
default <T> Either<Exception, T> executeEitherSupplier(Supplier<Either<? extends Exception, T>> supplier){
223+
return decorateEitherSupplier(this, supplier).get();
224+
}
225+
226+
/**
227+
* Returns a supplier which is decorated by a CircuitBreaker.
228+
*
229+
* @param supplier the original supplier
230+
* @param <T> the type of results supplied by this supplier
231+
*
232+
* @return a supplier which is decorated by a CircuitBreaker.
233+
*/
234+
default <T> Supplier<Try<T>> decorateTrySupplier(Supplier<Try<T>> supplier){
235+
return decorateTrySupplier(this, supplier);
236+
}
237+
238+
/**
239+
* Decorates and executes the decorated Supplier.
240+
*
241+
* @param supplier the original Supplier
242+
* @param <T> the type of results supplied by this supplier
243+
* @return the result of the decorated Supplier.
244+
*/
245+
default <T> Try<T> executeTrySupplier(Supplier<Try<T>> supplier){
246+
return decorateTrySupplier(this, supplier).get();
247+
}
248+
249+
/**
250+
* Returns a supplier which is decorated by a CircuitBreaker.
251+
*
252+
* @param supplier the original supplier
253+
* @param <T> the type of results supplied by this supplier
254+
*
255+
* @return a supplier which is decorated by a CircuitBreaker.
256+
*/
257+
default <T> Supplier<Either<Exception, T>> decorateEitherSupplier(Supplier<Either<? extends Exception, T>> supplier){
258+
return decorateEitherSupplier(this, supplier);
259+
}
260+
213261
/**
214262
* Decorates and executes the decorated Callable.
215263
*
@@ -666,6 +714,62 @@ static <T> Supplier<T> decorateSupplier(CircuitBreaker circuitBreaker, Supplier<
666714
};
667715
}
668716

717+
/**
718+
* Returns a supplier which is decorated by a CircuitBreaker.
719+
*
720+
* @param circuitBreaker the CircuitBreaker
721+
* @param supplier the original supplier
722+
* @param <T> the type of results supplied by this supplier
723+
*
724+
* @return a supplier which is decorated by a CircuitBreaker.
725+
*/
726+
static <T> Supplier<Either<Exception, T>> decorateEitherSupplier(CircuitBreaker circuitBreaker, Supplier<Either<? extends Exception, T>> supplier) {
727+
return () -> {
728+
if(circuitBreaker.tryAcquirePermission()) {
729+
circuitBreaker.acquirePermission();
730+
long start = System.nanoTime();
731+
Either<? extends Exception, T> result = supplier.get();
732+
long durationInNanos = System.nanoTime() - start;
733+
if (result.isRight()) {
734+
circuitBreaker.onSuccess(durationInNanos);
735+
} else {
736+
Exception exception = result.getLeft();
737+
circuitBreaker.onError(durationInNanos, exception);
738+
}
739+
return Either.narrow(result);
740+
}else{
741+
return Either.left(new CallNotPermittedException(circuitBreaker));
742+
}
743+
};
744+
}
745+
746+
/**
747+
* Returns a supplier which is decorated by a CircuitBreaker.
748+
*
749+
* @param circuitBreaker the CircuitBreaker
750+
* @param supplier the original function
751+
* @param <T> the type of results supplied by this supplier
752+
* @return a retryable function
753+
*/
754+
static <T> Supplier<Try<T>> decorateTrySupplier(CircuitBreaker circuitBreaker, Supplier<Try<T>> supplier) {
755+
return () -> {
756+
if(circuitBreaker.tryAcquirePermission()){
757+
long start = System.nanoTime();
758+
Try<T> result = supplier.get();
759+
long durationInNanos = System.nanoTime() - start;
760+
if(result.isSuccess()){
761+
circuitBreaker.onSuccess(durationInNanos);
762+
return result;
763+
}else{
764+
circuitBreaker.onError(durationInNanos, result.getCause());
765+
return result;
766+
}
767+
}else{
768+
return Try.failure(new CallNotPermittedException(circuitBreaker));
769+
}
770+
};
771+
}
772+
669773
/**
670774
* Returns a consumer which is decorated by a CircuitBreaker.
671775

0 commit comments

Comments
 (0)