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!

The official Project Reactor logo

In the era of Reactive Programming, one of the most common concerns is to spot blocking calls and embed them properly in reactive code or replace them with a reactive alternative when possible.

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:

<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>1.0.0.RC1</version>
<scope>test</scope>
</dependency>

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
at reactor.blockhound.BlockHound$Builder.lambda$new$0(BlockHound.java:167)
at reactor.blockhound.BlockHound$Builder.lambda$install$6(BlockHound.java:226)
at reactor.blockhound.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:46)
at java.lang.Thread.sleep(Thread.java)
at com.example.demo.DemoApplication.block(DemoApplication.java:30)

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!

BlockHound Customization

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:

You can define any rule you want (eg. allowing/disallowing specific blocking methods) by using the Builder API

And import the required Google AutoService dependency:

<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc1</version>
<scope>test</scope>
</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.

Conclusion

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!

References

For further reference, you can check out:

  • my demo project on GitHub, which shows how to customize BlockHound for JUnit testing and provides a more realistic example of blocking code hidden in a reactive application;
  • the official BlockHound documentation.

Software Developer @ https://www.linkedin.com/in/dsibilio/ — If my articles helped you, consider buying me a coffee :) https://www.buymeacoffee.com/dsibilio

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store