BlockHound Documentation

repository·master·Indexed 23 days ago

https://github.com/reactor/blockhound

A Java agent that detects blocking calls, such as I/O or Thread.sleep, on threads intended for non-blocking operations. It features built-in support for Project Reactor and RxJava, provides mechanisms to mark methods as blocking, and allows for custom non-blocking thread predicates and integrations via the BlockHoundIntegration interface.

Tokens
6K
Snippets
16
Records
25
Agent score
78%

What's inside BlockHound

  1. What is BlockHound and how does it work?

    master

    BlockHound is a Java agent designed to detect blocking calls (such as I/O operations) when they are executed from threads designated as "non-blocking operations only".

    It works by transparently instrumenting JVM classes and intercepting blocking calls. It identifies non-blocking threads by checking if they implement Reactor's NonBlocking marker interface (for example, threads started by Schedulers.parallel()). If a blocking call is detected on such a thread, BlockHound throws a reactor.blockhound.BlockingOperationError, which includes the exact location in your code where the violation occurred.

    // Example of a violation
    BlockHound.install();
    
    Mono.delay(Duration.ofSeconds(1))
        .doOnNext(it -> {
            try {
                Thread.sleep(10);
            }
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        })
        .block();
  2. How BlockHound detects blocking calls

    master

    BlockHound operates as a Java Agent that instruments a predefined set of blocking methods within the JVM. It works by injecting a check at the beginning of method bodies to determine if the current thread is permitted to execute a blocking operation.

    Java Method Detection

    For standard Java methods, BlockHound alters the bytecode to insert a call to reactor.blockhound.BlockHoundRuntime.checkBlocking at the start of the method body. For example, a java.net.Socket.connect call is instrumented to include a check before the actual connection logic is executed.

    Native Method Detection

    Since native methods (like java.lang.Thread.sleep) cannot be instrumented directly because they lack a Java method body, BlockHound uses a relocation technique:

    1. The original native method is renamed (e.g., to $$BlockHound$$_sleep).
    2. A new Java method is created with the original signature.
    3. This new method performs the checkBlocking call and then delegates to the renamed native implementation.

    This approach ensures minimal overhead, adding only a single method hop.

    // Example of how a Java method is instrumented
    public void connect(SocketAddress endpoint, int timeout) {
        reactor.blockhound.BlockHoundRuntime.checkBlocking(
            "java.net.Socket",
            "connect",
            /*method modifiers*/
        );
        // original implementation follows...
    }
  3. How to select what to whitelist

    master

    When certain blocking calls are unavoidable, you can use BlockHound's API to whitelist them. However, you must be careful: whitelisting common methods (like Thread#run) can lead to false negatives where actual blocking issues are missed.

    Best Practice: Instead of whitelisting common low-level APIs (like LinkedBlockingQueue#poll or LockSupport#parkNanos), which might affect other parts of your application, whitelist the least common denominator found in the stack trace.

    Identify the specific method in your application logic that triggers the blocking call but does not call user-provided code. For example, if a task runner calls executor.takeTask(), whitelisting TaskExecutor#takeTask is safer than whitelisting the underlying queue's poll method.

  4. How the blocking call decision is made

    master

    BlockHound uses a ThreadLocal<Boolean> named IS_ALLOWED to track whether blocking is permitted in the current thread. This allows the agent to support white-listed methods (like class loading) that might perform blocking operations without triggering an error.

    The IS_ALLOWED State

    • false: Blocking is not allowed in this thread. Any blocking call will trigger a report/error.
    • true: Blocking is explicitly allowed.
    • null: The thread state is indeterminate (used as an optimization for non-blocking threads).

    The Workflow

    1. Check: When an instrumented method is called, BlockHoundRuntime.checkBlocking reads the IS_ALLOWED value. If it is false, it reports the violation.
    2. Context Switching: When entering an "allowed" context (e.g., inside a ClassLoader), BlockHound captures the previous IS_ALLOWED state, sets it to true, executes the logic, and restores the previous state in a finally block.

    This mechanism ensures that the check is an $O(1)$ operation consisting of a single ThreadLocal read, making it performant enough for production use.

  5. Use BlockHound with JUnit 3 or JUnit 4

    master
    BlockHound does not have a native global lifecycle listener for JUnit 3 or JUnit 4. To use BlockHound with these older frameworks, you must run your JUnit 3/4 tests using the JUnit 5 Platform (via the JUnit Vintage engine). This allows you to leverage the blockhound-junit-platform integration described for the JUnit Platform.
  6. Install BlockHound via Maven or Gradle

    master

    To use BlockHound in your project, add it as a test dependency. Stable releases are available on Maven Central. Milestone and Snapshot versions are available on Spring's repositories.

    Use $LATEST_RELEASE for Maven Central, $LATEST_MILESTONE for Spring Milestones, or $LATEST_SNAPSHOT for Spring Snapshots.

    repositories {
      mavenCentral()
      // maven { url 'https://repo.spring.io/milestone' }
      // maven { url 'https://repo.spring.io/snapshot' }
    }
    
    dependencies {
      testImplementation 'io.projectreactor.tools:blockhound:$LATEST_RELEASE'
      // testImplementation 'io.projectreactor.tools:blockhound:$LATEST_MILESTONE'
      // testImplementation 'io.projectreactor.tools:blockhound:$LATEST_SNAPSHOT'
    }
  7. Customize BlockHound via JUnit Platform integrations

    master

    If you need to customize how BlockHound behaves when used with the JUnit Platform integration, you can implement a custom integration.

    1. Implement the reactor.blockhound.integration.BlockHoundIntegration interface.
    2. Register your implementation in META-INF/services/reactor.blockhound.integration.BlockHoundIntegration using the Service Provider Interface (SPI) pattern.
  8. Verify BlockHound integration in tests

    master

    When adding BlockHound to your project, always include a test that asserts the integration is working correctly. This prevents false positives caused by the agent being incorrectly installed or not installed at all. A simple way to verify this using Project Reactor is to schedule a task that performs a blocking operation (like Thread.sleep(0)) on a parallel scheduler and assert that it throws a BlockingOperationError.

    @Test
    public void blockHoundWorks() throws TimeoutException, InterruptedException {
        try {
            FutureTask<?> task = new FutureTask<>(() -> {
                Thread.sleep(0);
                return "";
            });
            Schedulers.parallel().schedule(task);
    
            task.get(10, TimeUnit.SECONDS);
            Assert.fail("should fail");
        } catch (ExecutionException e) {
            Assert.assertTrue("detected", e.getCause() instanceof BlockingOperationError);
        }
    }
  9. Integrate BlockHound with JUnit Platform (JUnit Jupiter or Vintage)

    master

    BlockHound provides an optional module that implements a JUnit Platform TestExecutionListener. When this module is present on the classpath, the BlockHound TestExecutionListener is automatically registered and executed by the JUnit Platform, which in turn invokes BlockHound.install() for your tests.

    To use this, add the blockhound-junit-platform artifact to your dependencies.

    Note for Gradle users: Due to a known Gradle bug, you must explicitly add org.junit.platform:junit-platform-launcher (version 1.0.0 or higher) as a testRuntime dependency.

  10. Use BlockHound with Tomcat

    master

    When running BlockHound in a Tomcat webapp, do not embed the BlockHound dependency within the webapp itself. Instead, place the BlockHound JAR in the Tomcat shared lib directory.

    If using the Cargo Maven plugin, you can configure a shared classpath for the dependency.

    <dependencies>
        <dependency>
            <groupId>io.projectreactor.tools</groupId>
            <artifactId>blockhound</artifactId>
            <version>(latest blockhound version)</version>
            <scope>provided</scope>
        </dependency>
        ...
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>1.10.4</version>
                <configuration>
                    <container>
                        <containerId>tomcat9x</containerId>
                        <type>embedded</type>
                        <dependencies>
                            <dependency>
                                <groupId>io.projectreactor.tools</groupId>
                                <artifactId>blockhound</artifactId>
                                <classpath>shared</classpath>
                            </dependency>
                        </dependencies>
                    </container>
                    <deployables>
                        <deployable>
                            <type>war</type>
                            <location>${project.build.directory}/${project.build.finalName}.war</location>
                            <properties>
                                <context>/</context>
                            </properties>
                        </deployable>
                    </deployables>
                </configuration>
            </plugin>
        </plugins>
    </build>
  11. Install BlockHound

    master

    BlockHound can be installed in three ways depending on whether you want automatic integration discovery or manual control:

    1. Standard Installation: Uses ServiceLoader to automatically load all known reactor.blockhound.integration.BlockHoundIntegration implementations.
    2. Custom Integrations: Adds user-provided integrations to the automatically discovered list.
    3. Manual/Clean Installation: Uses a builder to create a fresh instance without discovering any default integrations. You must manually add any integrations you need using .with(new MyIntegration()).
  12. Install BlockHound via Maven

    master

    Add the following dependency to your pom.xml to use BlockHound in a Maven project.

    <dependency>
      <groupId>io.projectreactor.tools</groupId>
      <artifactId>blockhound</artifactId>
      <version>$LATEST_RELEASE</version>
    </dependency>