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:
- Go to https://start.spring.io/
- Project : Maven
- Dependendencies: Webflux
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.