private Object handleOther(MethodInvocation invocation, io.github.resilience4j.bulkhead.Bulkhead bulkhead, RecoveryFunction<?> recoveryFunction) throws Throwable { boolean permission = bulkhead.isCallPermitted(); if (!permission) { Throwable t = new BulkheadFullException(String.format("Bulkhead '%s' is full", bulkhead.getName())); return recoveryFunction.apply(t); } try { if (Thread.interrupted()) { throw new IllegalStateException("Thread was interrupted during permission wait"); } return invocation.proceed(); } catch (Exception e) { return recoveryFunction.apply(e); } finally { bulkhead.onComplete(); } } }
private BulkheadMetrics(String prefix, Iterable<Bulkhead> bulkheads) { requireNonNull(prefix); requireNonNull(bulkheads); bulkheads.forEach(bulkhead -> { String name = bulkhead.getName(); //number of available concurrent calls as an integer metricRegistry.register(name(prefix, name, AVAILABLE_CONCURRENT_CALLS), (Gauge<Integer>) () -> bulkhead.getMetrics().getAvailableConcurrentCalls()); } ); }
@Test public void testCreateWithDefaults() { // when Bulkhead bulkhead = Bulkhead.ofDefaults("test"); // then assertThat(bulkhead).isNotNull(); assertThat(bulkhead.getBulkheadConfig()).isNotNull(); }
public static void isCallPermitted(Bulkhead bulkhead) { if(!bulkhead.isCallPermitted()) { throw new BulkheadFullException(String.format("Bulkhead '%s' is full", bulkhead.getName())); } } }
@Test public void shouldNotReleaseBulkheadWhenWasDisposedAfterNotPermittedSubscribe() throws Exception { // Given Disposable disposable = mock(Disposable.class); MaybeObserver childObserver = mock(MaybeObserver.class); MaybeObserver decoratedObserver = BulkheadOperator.of(bulkhead).apply(childObserver); bulkhead.isCallPermitted(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(0); decoratedObserver.onSubscribe(disposable); // When ((Disposable) decoratedObserver).dispose(); // Then assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(0); }
@Test public void testBulkhead() throws InterruptedException { bulkhead.isCallPermitted(); bulkhead.isCallPermitted(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(0); bulkhead.isCallPermitted(); bulkhead.onComplete(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); bulkhead.onComplete(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(2); bulkhead.isCallPermitted(); testSubscriber.assertValueCount(6) .assertValues(CALL_PERMITTED, CALL_PERMITTED, CALL_REJECTED, CALL_FINISHED, CALL_FINISHED, CALL_PERMITTED); }
private void releaseBulkhead() { if (permitted.compareAndSet(Permit.ACQUIRED, Permit.RELEASED)) { bulkhead.onComplete(); } } }
@Override protected Throwable getThrowable() { return new BulkheadFullException(String.format("Bulkhead '%s' is full", bulkhead.getName())); }
@Test public void shouldReturnFailureWithRuntimeException() { // Given BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(2).build(); Bulkhead bulkhead = Bulkhead.of("test", config); bulkhead.isCallPermitted(); //v When CheckedRunnable checkedRunnable = Bulkhead.decorateCheckedRunnable(bulkhead, () -> {throw new RuntimeException("BAM!");}); Try result = Try.run(checkedRunnable); //Then assertThat(result.isFailure()).isTrue(); assertThat(result.failed().get()).isInstanceOf(RuntimeException.class); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); }
@Test public void shouldReturnTheCorrectName() { Bulkhead bulkhead = registry.bulkhead("test"); assertThat(bulkhead).isNotNull(); assertThat(bulkhead.getName()).isEqualTo("test"); assertThat(bulkhead.getBulkheadConfig().getMaxConcurrentCalls()).isEqualTo(25); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(25); }
@Test public void shouldConsumeOnCallRejectedEvent() { // Given Bulkhead bulkhead = Bulkhead.of("test", config); // When bulkhead.getEventPublisher() .onCallRejected(event -> logger.info(event.getEventType().toString())); bulkhead.isCallPermitted(); Try.ofSupplier(Bulkhead.decorateSupplier(bulkhead,helloWorldService::returnHelloWorld)); // Then then(logger).should(times(1)).info("CALL_REJECTED"); }
@Test public void shouldInvokeAsyncApply() throws ExecutionException, InterruptedException { // tag::shouldInvokeAsyncApply[] // Given Bulkhead bulkhead = Bulkhead.of("test", config); // When Supplier<String> decoratedSupplier = Bulkhead.decorateSupplier(bulkhead, () -> "This can be any method which returns: 'Hello"); CompletableFuture<String> future = CompletableFuture.supplyAsync(decoratedSupplier) .thenApply(value -> value + " world'"); String result = future.get(); // Then assertThat(result).isEqualTo("This can be any method which returns: 'Hello world'"); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); // end::shouldInvokeAsyncApply[] }
@Test public void shouldDecorateCompletionStageAndReturnWithExceptionAtAsyncStage() throws ExecutionException, InterruptedException { // Given Bulkhead bulkhead = Bulkhead.of("test", config); BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new RuntimeException("BAM! At async stage")); // When Supplier<CompletionStage<String>> completionStageSupplier = () -> CompletableFuture.supplyAsync(helloWorldService::returnHelloWorld); Supplier<CompletionStage<String>> decoratedCompletionStageSupplier = Bulkhead.decorateCompletionStage(bulkhead, completionStageSupplier); CompletionStage<String> decoratedCompletionStage = decoratedCompletionStageSupplier.get(); // Then the helloWorldService should be invoked 1 time assertThatThrownBy(decoratedCompletionStage.toCompletableFuture()::get) .isInstanceOf(ExecutionException.class).hasCause(new RuntimeException("BAM! At async stage")); BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); }
); chain1.get("stream/events", ctx -> { Seq<Flux<BulkheadEvent>> eventStreams = bulkheadRegistry.getAllBulkheads().map(bulkhead -> ReactorAdapter.toFlux(bulkhead.getEventPublisher())); Function<BulkheadEvent, String> data = b -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(BulkheadEventDTO.createEventDTO(b)); ServerSentEvents events = ServerSentEvents.serverSentEvents(Flux.merge(eventStreams), e -> e.id(BulkheadEvent::getBulkheadName).event(c -> c.getEventType().name()).data(data)); chain1.get("stream/events/:name", ctx -> { String bulkheadName = ctx.getPathTokens().get("name"); Bulkhead bulkhead = bulkheadRegistry.getAllBulkheads().find(b -> b.getName().equals(bulkheadName)) .getOrElseThrow(() -> new IllegalArgumentException(String.format("bulkhead with name %s not found", bulkheadName))); Function<BulkheadEvent, String> data = b -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(BulkheadEventDTO.createEventDTO(b)); ServerSentEvents events = ServerSentEvents.serverSentEvents(ReactorAdapter.toFlux(bulkhead.getEventPublisher()), e -> e.id(BulkheadEvent::getBulkheadName).event(c -> c.getEventType().name()).data(data)); ctx.render(events); }); String bulkheadName = ctx.getPathTokens().get("name"); String eventType = ctx.getPathTokens().get("type"); Bulkhead bulkhead = bulkheadRegistry.getAllBulkheads().find(b -> b.getName().equals(bulkheadName)) .getOrElseThrow(() -> new IllegalArgumentException(String.format("bulkhead with name %s not found", bulkheadName))); Flux<BulkheadEvent> eventStream = ReactorAdapter.toFlux(bulkhead.getEventPublisher()) .filter(event -> event.getEventType() == BulkheadEvent.Type.valueOf(eventType.toUpperCase())); Function<BulkheadEvent, String> data = b -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(BulkheadEventDTO.createEventDTO(b));
@Test public void shouldEmitEvent() { StepVerifier.create( Mono.just("Event") .transform(BulkheadOperator.of(bulkhead))) .expectNext("Event") .verifyComplete(); assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); }
@Test public void shouldDecorateRunnableAndReturnWithSuccess() throws Throwable { // Given Bulkhead bulkhead = Bulkhead.of("test", config); //When Bulkhead.decorateRunnable(bulkhead, helloWorldService::sayHelloWorld) .run(); //Then assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); BDDMockito.then(helloWorldService).should(times(1)).sayHelloWorld(); }
@Override protected boolean isCallPermitted() { return bulkhead.isCallPermitted(); }
@Test public void shouldConsumeOnCallFinishedEventOnComplete() throws Exception { // Given Bulkhead bulkhead = Bulkhead.of("test", config); // When bulkhead.getEventPublisher() .onCallFinished(event -> logger.info(event.getEventType().toString())); bulkhead.onComplete(); // Then then(logger).should(times(1)).info("CALL_FINISHED"); } }
@Test public void shouldDecorateConsumerAndReturnWithSuccess() throws Throwable { // Given Bulkhead bulkhead = Bulkhead.of("test", config); // When Bulkhead.decorateConsumer(bulkhead, helloWorldService::sayHelloWorldWithName) .accept("Tom"); // Then assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1); BDDMockito.then(helloWorldService).should(times(1)).sayHelloWorldWithName("Tom"); }
@Test(expected = NullPointerException.class) public void testConstructorWithNullName() { BulkheadExports.ofSupplier(null, () -> singleton(Bulkhead.ofDefaults("foo"))); }