반응형

 

BlockHound 정리해야지,, 생각만 하다가 어느덧 일주일이 흘러버렸습니다,,

 

문제는 BlockHound,, 기억이 나지 않는다는 것^^,,,

 

이런걸 진정한 복습이라 생각하며, 기억과 코드를 찾아보며 정리해 보고자합니다,,

 

할 수 있다,, 기억해 낼 수 있다,, 아자아자,,🐦‍🔥

 

1. BlockHound 정의

개발자가 직접 작성한 코드를 포함하여 JDK, Thrid-party 라이브러리에 사용된 블로킹 메소드 호출을 모두 찾아내서 알려주는 도구

💡 Spring Webflux 기반의 애플리케이션은 모든 코드가 Reactive 방식으로 작동해야 최상의 성능이 나옵니다. 즉, blocking 코드가 존재한다면 충분한 성능이 나오지 않을 수도 있다는 것입니다. 이를 위해 BlockHound를 사용합니다.

 

❓Reactive 방식
비동기(Asynchronous)
논블로킹(Non-blocking)
데이터 스트림(Streams): 데이터를 필요한 만큼 조금씩 전달하며 처리하는 방식
Backpressure: 데이터 소비자가 처리할 수 있는 속도를 조절하는 기능

 

https://github.com/reactor/BlockHound

 

GitHub - reactor/BlockHound: Java agent to detect blocking calls from non-blocking threads.

Java agent to detect blocking calls from non-blocking threads. - reactor/BlockHound

github.com

 

 

2. BlockHound 설정

build.gradle
테스트 시, 사용 예정이므로 testImplementation을 선언
버전은 Github를 참고하여 최신 버전을 입력(25.02.09기준 최신 버전 - 1.0.10)
dependencies {
	testImplementation 'io.projectreactor.tools:blockhound:1.0.10.RELEASE'
}

 

 

3. 테스트 코드 작성

static void setUp() {
    BlockHound.install();
}

@Test
void shouldDetectBlockingCall() {
  Mono<Long> blockingMono = Mono.delay(Duration.ofSeconds(1))
                                .doOnNext(t -> {
                                  try {
                                    Thread.sleep(100);  // 블로킹 호출
                                  } catch (InterruptedException e) {
                                    throw new RuntimeException(e);
                                  }
                                });

  StepVerifier.create(blockingMono)
              .expectError(BlockingOperationError.class)
              .verify();
}

 

하지만, 살다보면 Webflux에서도 Blocking을 허용해야하는 순간이 올 것입니다..🍂

static {
    BlockHound.install(builder ->
        builder.allowBlockingCallsInside(UserControllerTest.class.getName(), "shouldAllowBlockingCallInsideAllowedMethod")
    );
}

@Test
void shouldAllowBlockingCallInsideAllowedMethod() {
  Mono<Long> allowedMono = Mono.delay(Duration.ofSeconds(1))
                               .doOnNext(t -> {
                                 try {
                                   Thread.sleep(100);
                                 } catch (InterruptedException e) {
                                   throw new RuntimeException(e);
                                 }
                               });

  StepVerifier.create(allowedMono)
              .expectNextCount(0)
              .verifyComplete();
}

 

🚨 분명 Blocking을 허용하겠다고 했는데, 아래와 같은 오류가 발생합니다.

expectation "expectComplete" failed (expected: onComplete(); 
actual: onError(reactor.blockhound.BlockingOperationError: Blocking call! java.lang.Thread.sleep))

 

allowBlockingCallsInside는 특정 클래스의 특정 메서드 내부에서 발생하는 블로킹 호출만 허용하는데, shouldAllowBlockingCallInsideAllowedMethod안에 있는 Thread.sleep(100)은 doOnNext의 람다 내부에서 호출되고 있습니다.

이 때, BlockHound는 람다 표현식을 익명 클래스로 취급하기 때문에, allowBlockingCallsInside가 해당 호출을 허용하지 않게 되어 오류를 발생시킵니다.

 

따라서 블로킹이 발생하는 부분을 따로 메서드로 분리합니다.

static {
    BlockHound.install(builder ->
        builder.allowBlockingCallsInside(UserControllerTest.class.getName(), "allowedBlockingMethod")
);

// 블로킹 호출을 별도 메서드로 분리
void allowedBlockingMethod() {
  try {
    Thread.sleep(100);
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
}

@Test
void shouldAllowBlockingCallInsideAllowedMethod() {
  Mono<Long> allowedMono = Mono.delay(Duration.ofSeconds(1)) // 1초 후에 0L이라는 값을 방출
                                     .doOnNext(__ -> allowedBlockingMethod());

  StepVerifier.create(allowedMono)
              .expectNextCount(1)
              .verifyComplete();
}

 

 

 

📑

참고 자료

Chat GPT

https://velog.io/@be_have98/Spring-Webflux-BlockHound-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Spring Webflux] BlockHound 적용하기

비동기 프로그램을 구현하기 위해 블로킹 메소드를 검출하는 BlockHound를 적용해보자

velog.io

https://velog.io/@be_have98/Spring-Webflux-BlockHound-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Spring Webflux] BlockHound 적용하기

비동기 프로그램을 구현하기 위해 블로킹 메소드를 검출하는 BlockHound를 적용해보자

velog.io

 

반응형