blog.frankel.ch Open in urlscan Pro
2606:4700:3035::6815:1f2d  Public Scan

Submitted URL: http://blog.frankel.ch/blockhound-how-it-works
Effective URL: https://blog.frankel.ch/blockhound-how-it-works/
Submission: On March 01 via api from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

This website uses cookies to ensure you get the best experience on our website.
Learn more
Got it!
A Java geek
 * Me
 * Books
 * Speaking
 * Mentions
 * Focus


Jun 20, 2021 / BLOCKHOUND, REACTIVE PROGRAMMING


BLOCKHOUND: HOW IT WORKS

One of the talks in my current portfolio is Migrating from Imperative to
Reactive. The talk is based on a demo migrating from Spring WebMVC to Spring
WebFlux in a step-by-step approach. One of the steps involves installing
BlockHound: it allows to check whether a blocking call occurs in a thread it
shouldn’t happen and throws an exception at runtime when it happens.

I’ve presented this talk several times in the previous week, both in its Java
version and its Kotlin one. One such presentation was at Javaday Istanbul. After
the talk, one of the questions I received was, "How does BlockHound work?" I
admit that at the time, I couldn’t remember. After the surprise had passed, but
too late, I remembered it involved a Java agent. But I wanted to go down the
rabbit hole.

This post tries to explain what I discovered.


WHY BLOCKHOUND?

Reactive Programming is based on asynchronous message passing. Different
frameworks/libraries will differ in their approach: for example, in Project
Reactor, an API call is not a blocking request-response call but a subscription
to a message(s) that the publisher will deliver in the future.

A standard call chain rarely involves only a publisher and a subscriber. In
general, there are multiple steps between them. Each intermediate step, called a
processor, acts as a subscriber for the next step and a producer for the
previous one. When the target producer emits, it sends a message to its
subscriber, which sends a message to its subscriber, and so on, up to the first
subscriber in the chain.

If one call in the chain is blocking, it "freezes" the whole chain until the
work has finished. In that case, it dramatically diminishes the benefits of the
Reactive approach. Enters BlockHound:

> BlockHound will transparently instrument the JVM classes and intercept
> blocking calls (e.g. IO) if they are performed from threads marked as
> "non-blocking operations only" (ie. threads implementing Reactor’s NonBlocking
> marker interface, like those started by Schedulers.parallel()). If and when
> this happens (but remember, this should never happen!😜), an error will be
> thrown.

In few words, BlockHound checks for blocking calls in places where there
shouldn’t be.


USING BLOCKHOUND

Using BlockHound is straightforward:

 * Add the dependency to your classpath
 * Call BlockHound.install()

At this point, I naively figured that every blocking call would throw an
exception. That’s not the case. You can see for yourself by executing the
following code:

public static void main(String[] args) throws InterruptedException {
    BlockHound.install();
    Thread.currentThread().sleep(200);
}

Though sleep() is blocking, the program executes normally.

Getting a bit into BlockHound’s source code reveals that sleep() is indeed
configured as blocking (along with several others):

reactor.blockhound.BlockHound.Builder

public static class Builder {

    private final Map<String, Map<String, Set<String>>> blockingMethods = new HashMap<String, Map<String, Set<String>>>() );
}

So, what’s the problem?


BLOCKING CALLS ON THE "MAIN" THREAD

Blocking calls are not a problem per se. A lot of calls are blocking but
required anyway. It wouldn’t be possible to throw on every blocking call.

The problem is to waste CPU cycles unnecessarily:

 1. You call a blocking method
 2. The method takes a long time to finish, e.g., make an HTTP call
 3. Until the call returns, you cannot execute other block sections

Hence, blocking methods should run on their dedicated thread. This approach
wastes nothing.

Because of that, BlockHound doesn’t throw on every blocking call but only on
such calls that happen on threads that should be non-blocking. To mark a thread
as non-blocking, we need to configure a predicate.

public static void main(String[] args) throws InterruptedException {
    BlockHound.install(builder -> {
        builder.nonBlockingThreadPredicate(current ->
                current.or(thread -> thread.getName().equals("main"))); (1)
    });
    Thread.currentThread().sleep(200);
}

1 The main thread is marked as non-blocking

Running the above code expectedly throws the following:

Exception in thread "main" reactor.blockhound.BlockingOperationError: \
  Blocking call! java.lang.Thread.sleep
	at java.base/java.lang.Thread.sleep(Thread.java)
	at ch.frankel.blog.blockhound.B.main(B.java:11)

To be entirely sure, let’s tweak our code to mark another thread only as
non-blocking. sleep() in main shouldn’t throw while sleep() in this other thread
should.

public static void main(String[] args) throws InterruptedException {
    var name = "non-blocking";
    BlockHound.install(builder -> {
        builder.nonBlockingThreadPredicate(current ->
                current.or(thread -> thread.getName().equals(name)));   (1)
    });
    var thread = new Thread(() -> {
        try {
            System.out.println("Other thread started");
            Thread.currentThread().sleep(2000);                         (2)
            System.out.println("Other thread finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, name);
    thread.start();                                                     (3)
    Thread.currentThread().sleep(200);                                  (4)
    System.out.println("Main thread finished");
}

1 Flag threads with the parameterized name as non-blocking 2 Expected to throw 3
Start the new thread 4 Expect not to throw

As expected, the previous code outputs the following log:

Other thread started
Exception in thread "non-blocking" reactor.blockhound.BlockingOperationError: \
  Blocking call! java.lang.Thread.sleep
	at java.base/java.lang.Thread.sleep(Thread.java)
	at ch.frankel.blog.blockhound.C.lambda$main$3(C.java:16)
	at java.base/java.lang.Thread.run(Thread.java:829)
Main thread finished


BLOCKHOUND IS ONLY AS GOOD AS ITS CONFIGURATION

Though its artifact’s groupId is io.projectreactor, BlockHound is generic:

 * The JAR is self-contained. The only dependency is ByteBuddy, which BlockHound
   shades.
 * BlockHound provides an integration abstraction. Integrations allow providing
   configuration bits. It can be generic or specific to a framework, e.g.
   RxJava, Reactor, etc.
 * Integrations are loaded with the Java’s service loader

With that, it’s time to check the BlockHound API a bit.



For example, the RxJava2Integration integration configures BlockHound for RxJava
2:

reactor.blockhound.integration.RxJava2Integration

builder.nonBlockingThreadPredicate(current -> current.or(NonBlockingThread.class::isInstance));


BUT HOW DOES IT WORK?

It’s time to get back to our original question, how does BlockHound work?

Here’s what happens when you call BlockHound.install():



The magic happens in the instrument() method. There, BlockHound uses bytecode
instrumentation to inject a code snippet in front of every method marked as
blocking. A method is blocking if:

 * We explicitly mark it as such by calling Builder.markAsBlocking()
 * An automatically-loaded integration calls the above API for us

The snippet’s code flow looks like this:


 1. If the call is allowed, the flow continues normally.
 2. If the call is dynamic, it also continues normally.
 3. Otherwise, i.e., the call is blocking and not dynamic, BlockHound throws.


CONCLUSION

Using BlockHound is straightforward, i.e., BlockHound.install(). A short
explanation is that BlockHound is a Java agent.

But in this post, we dug a bit under the cover to understand how this simple
call worked.

 1. BlockHound’s instrumentation adds code to methods marked as blocking.
 2. When they run in non-dynamic threads, BlockHound throws.

Thanks a lot to Violeta Georgieva for her review!


TO GO FURTHER:

 * How it works
 * Custom integrations



NICOLAS FRÄNKEL

Developer Advocate with 15+ years experience consulting for many different
customers, in a wide range of contexts (such as telecoms, banking, insurances,
large retail and public sector). Usually working on Java/Java EE and Spring
technologies, but with focused interests like Rich Internet Applications,
Testing, CI/CD and DevOps. Also double as a trainer and triples as a book
author.

Read More

Jun 27, 2021 Rust exercises practice learning


THE RUSTLINGS EXERCISES - PART 2

This is the 4th post in the Start Rust focus series. Last week, I started the
Rustlings series of exercises. Now is the time to finish it.

Nicolas Fränkel
Jun 13, 2021 Rust exercises practice learning beginners


THE RUSTLINGS EXERCISES - PART 1

This is the 3rd post in the Start Rust focus series. To continue building my
understanding of Rust, I searched for some simple Rust exercises. Hence, I
dedicated my weekly personal work time to the Rustling exercises. There will be
two posts dedicated to Rustlings. The reason for that is that it contains many
(many!) exercises. Besides, I need to learn about the more advanced themes such
as threading.

Nicolas Fränkel
A Java geek
—
BlockHound: how it works
Share this

A Java geek © 2008-2023
v. 8c737b1842c927e3fe702ea0b3c887fd01aeb884/3836173642
Latest Posts