BlockHound: detect blocking calls in Reactive code before it’s too late
Find the blockage that clogs your reactive streams pipelines before it hits production!
It’s really easy to miss some of these blocking operations and end up shipping to production something that shouldn’t be considered production-ready at all: scheduling blocking calls inside non-blocking threads can lead to unpredictable behaviour, at best!
To avoid this happening to you, you should strongly consider relying on a tool like BlockHound to detect blocking calls in reactive code.
How does it work?
BlockHound acts similarly to a java agent, a special little piece of software that gets loaded by the JVM before invoking the main entry point of your application and relies on the JVM Instrumentation API to orchestrate byte-code manipulation on classes and redefine their behaviour.
Once BlockHound gets bootstrapped, it marks a set of blocking methods that need to be altered (eg.
Thread.sleep()) and changes their behaviour so they’ll end up throwing an Error in case they get invoked from within a thread that’s marked as Non-Blocking.
If this all sounds like black magic to you, don’t worry!
Starting BlockHound is as easy as typing
BlockHound.install() inside your
main method — or typing nothing at all, as you’ll see in a minute.
How to use BlockHound
Let’s create a demo application that schedules a blocking operation twice — once using the Schedulers.elastic() regular thread pool and once using the Schedulers.parallel() Non-Blocking thread pool from Project Reactor.
Head over to start.spring.io and generate a new Spring Boot 2.2.0.M6 project with just Spring Reactive Web as a dependency.
Now, add the BlockHound JUnit Platform dependency to your pom.xml file:
And let’s add some code to the only class that was generated by start.spring.io (eg. DemoApplication.java) until it looks like the following:
Finally, create a JUnit test class that resembles the one below:
The first test method verifies that blocking code is not allowed when executed from within a Non-Blocking thread belonging to the
Schedulers.parallel() thread pool.
The second test method verifies that blocking code is allowed when executed from within a regular thread belonging to the
Schedulers.elastic() thread pool.
If you run the tests with the
mvn clean test command, you’ll notice they’ll all pass!
You should also see a stack trace like the one just below, showing BlockHound detected a blocking call and turned it into an Error-throwing call.
java.lang.Error: Blocking call! java.lang.Thread.sleep
You didn’t have to write a single line of code to make BlockHound do its magic since the blockhound-junit-platform dependency specifically sets up a JUnit TestExecutionListener that invokes the
BlockHound.install() method as soon as possible, in your stead. Neat!
In order to customize BlockHound’s behaviour and adapt it to our needs, you can create custom BlockHoundIntegrations that allow you to define your own rules.
To do so, you’ll have to create a class that implements
BlockHoundIntegration somewhere in your tests classpath:
And import the required Google AutoService dependency:
What actually happens behind the scenes is this: the BlockHound JUnit Platform test execution listener bootstraps BlockHound by calling
BlockHound.install(), which in turn relies on the java.util.ServiceLoader to load all implementations of the
BlockHoundIntegration interface that are listed inside the META-INF/services/reactor.blockhound.integration.BlockHoundIntegration file.
You didn’t need to create such file to list
AllowLoggingIntegration as a custom integration because the
@AutoService annotation generates the configuration files on the fly in your stead.
The benefits are great, BlockHound can potentially save you precious hours of troubleshooting that end up with discovering hidden blocking calls in seemingly endless stacks.
It also allows you to fail fast and avoids some nasty bugs making their way to production.
The cost to setup BlockHound and tweak its settings until you’re satisfied with it is greatly outweighed by its benefits.
There is no reason whatsoever to not use something like BlockHound while testing reactive applications!
For further reference, you can check out: