Introduction to BlockHound

Introduction

In this article, you learn how detect if your reactive non-blocking Spring app has blocking calls. To achieve this I will use BlockHound

BlockHound is a Java agent that works intercepting the blocking calls from JVM classes BlockHound

Requirements:

  • Support JDK13+
  • Use Project Reactor or RxJava2

1. Generate the base code:

2. Add dependencies:

  • pom.xml
1<dependency>  
2    <groupId>io.projectreactor.tools</groupId>  
3    <artifactId>blockhound</artifactId>  
4    <version>1.0.11.RELEASE</version>  
5</dependency>
  • pom.xml build section The argument -XX:+AllowRedefinitionToAddDeleteMethods is added to maven-surefire-plugin configuration because it is responsible for test execution. Also, -XX:+AllowRedefinitionToAddDeleteMethods is included in the spring-boot-maven-plugin configuration because it is necessary when packaged application is executed
 1 <build>
 2	<plugins>
 3...
 4		<plugin>
 5			<groupId>org.apache.maven.plugins</groupId>
 6			<artifactId>maven-surefire-plugin</artifactId>
 7			<version>3.1.2</version>
 8			<configuration>
 9				<argLine>-XX:+AllowRedefinitionToAddDeleteMethods</argLine>
10			</configuration>
11		</plugin>
12		<plugin>
13			<groupId>org.springframework.boot</groupId>
14			<artifactId>spring-boot-maven-plugin</artifactId>
15			<configuration>
16				<jvmArguments>-XX:+AllowRedefinitionToAddDeleteMethods</jvmArguments>
17			</configuration>
18		</plugin>
19	</plugins>
20</build>

3. Configuration

BlockhoundDemoApplication.java

1@SpringBootApplication  
2public class BlockhoundDemoApplication {  
3  
4    public static void main(String[] args) {  
5       BlockHound.install();  //add to install BlockHound
6       SpringApplication.run(BlockhoundDemoApplication.class, args);  
7    }  
8  
9}

4. Testing BlockHound

For testing BlockHound is working properly, I used a simple blocking and non-blocking code:

 1@RestController  
 2public class BlockingController {  
 3  
 4    @GetMapping("/blocking")  
 5    public Mono<String> blockingEndpoint() {  
 6        return Mono.just("Starting blocking code...")  
 7                .doOnNext(s -> {  
 8                    try {  
 9                        Thread.sleep(2000);  
10                    } catch (InterruptedException e) {  
11                        Thread.currentThread().interrupt();  
12                    }  
13                })  
14                .map(s -> s + "Finishing blocking code...");  
15    }  
16  
17    @GetMapping("/non-blocking")  
18    public Mono<String> nonBlockingEndpoint() {  
19        return Mono.just("Non-blocking code");  
20    }  
21}

5. JUnit Jupiter and BlockHound

Add this method as static for work with only one BlockHound instance .

1@BeforeAll  
2static void setupBlockHound() {  
3    BlockHound.install();  
4}

6. Results

After call http://localhost:8080/blocking we can see this:

1reactor.blockhound.BlockingOperationError: Blocking call! java.lang.Thread.sleepNanos0
2	at java.base/java.lang.Thread.sleepNanos0(Thread.java) ~[na:na]
3	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
4Error has been observed at the following site(s):
5	*__checkpoint ⇢ Handler dev.davidsarmiento.blockhounddemo.controller.BlockingController#blockingEndpoint() [DispatcherHandler]
6	*__checkpoint ⇢ HTTP GET "/blocking" [ExceptionHandlingWebHandler]

Conclusions

  • BlockHound dectect propertly the non-blocking code in our Spring WebFlux App.
  • BlockHound can be used in the main application as well as on unit tests.

Source code

Github source

David Sarmiento Patron

Software Engineer, Java, Cloud, Microservices

@rappi Peru