Add BlockHound integration that detects blocking calls in event loops (#9687)
Motivation: Netty is an asynchronous framework. If somebody uses a blocking call inside Netty's event loops, it may lead to a severe performance degradation. BlockHound is a tool that helps detecting such calls. Modifications: This change adds a BlockHound's SPI integration that marks threads created by Netty (`FastThreadLocalThread`s) as non-blocking. It also marks some of Netty's internal methods as whitelisted as they are required to run the event loops. Result: When BlockHound is installed, any blocking call inside event loops is intercepted and reported (by default an error will be thrown).
This commit is contained in:
parent
aba9b34a59
commit
f4b536edcb
@ -78,6 +78,11 @@
|
|||||||
<artifactId>log4j-core</artifactId>
|
<artifactId>log4j-core</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor.tools</groupId>
|
||||||
|
<artifactId>blockhound</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
|
87
common/src/main/java/io/netty/util/internal/Hidden.java
Normal file
87
common/src/main/java/io/netty/util/internal/Hidden.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.util.internal;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
|
import reactor.blockhound.BlockHound;
|
||||||
|
import reactor.blockhound.integration.BlockHoundIntegration;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains classes that must be have public visibility but are not public API.
|
||||||
|
*/
|
||||||
|
class Hidden {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class integrates Netty with BlockHound.
|
||||||
|
* <p>
|
||||||
|
* It is public but only because of the ServiceLoader's limitations
|
||||||
|
* and SHOULD NOT be considered a public API.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
@SuppressJava6Requirement(reason = "BlockHound is Java 8+, but this class is only loaded by it's SPI")
|
||||||
|
public static final class NettyBlockHoundIntegration implements BlockHoundIntegration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyTo(BlockHound.Builder builder) {
|
||||||
|
builder.allowBlockingCallsInside(
|
||||||
|
"io.netty.channel.nio.NioEventLoop",
|
||||||
|
"handleLoopException"
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.allowBlockingCallsInside(
|
||||||
|
"io.netty.channel.kqueue.KQueueEventLoop",
|
||||||
|
"handleLoopException"
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.allowBlockingCallsInside(
|
||||||
|
"io.netty.channel.epoll.EpollEventLoop",
|
||||||
|
"handleLoopException"
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.allowBlockingCallsInside(
|
||||||
|
"io.netty.util.HashedWheelTimer$Worker",
|
||||||
|
"waitForNextTick"
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.allowBlockingCallsInside(
|
||||||
|
"io.netty.util.concurrent.SingleThreadEventExecutor",
|
||||||
|
"confirmShutdown"
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.nonBlockingThreadPredicate(new Function<Predicate<Thread>, Predicate<Thread>>() {
|
||||||
|
@Override
|
||||||
|
public Predicate<Thread> apply(final Predicate<Thread> p) {
|
||||||
|
return new Predicate<Thread>() {
|
||||||
|
@Override
|
||||||
|
@SuppressJava6Requirement(reason = "Predicate#test")
|
||||||
|
public boolean test(Thread thread) {
|
||||||
|
return p.test(thread) || thread instanceof FastThreadLocalThread;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(BlockHoundIntegration o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2019 The Netty Project
|
||||||
|
#
|
||||||
|
# The Netty Project licenses this file to you under the Apache License,
|
||||||
|
# version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at:
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
io.netty.util.internal.Hidden$NettyBlockHoundIntegration
|
8
pom.xml
8
pom.xml
@ -375,6 +375,7 @@
|
|||||||
<module>testsuite-osgi</module>
|
<module>testsuite-osgi</module>
|
||||||
<module>testsuite-shading</module>
|
<module>testsuite-shading</module>
|
||||||
<module>testsuite-native-image</module>
|
<module>testsuite-native-image</module>
|
||||||
|
<module>transport-blockhound-tests</module>
|
||||||
<module>microbench</module>
|
<module>microbench</module>
|
||||||
<module>bom</module>
|
<module>bom</module>
|
||||||
</modules>
|
</modules>
|
||||||
@ -674,6 +675,13 @@
|
|||||||
<version>${log4j2.version}</version>
|
<version>${log4j2.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- BlockHound integration -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor.tools</groupId>
|
||||||
|
<artifactId>blockhound</artifactId>
|
||||||
|
<version>1.0.1.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
53
transport-blockhound-tests/pom.xml
Normal file
53
transport-blockhound-tests/pom.xml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2019 The Netty Project
|
||||||
|
~
|
||||||
|
~ The Netty Project licenses this file to you under the Apache License,
|
||||||
|
~ version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
~ with the License. You may obtain a copy of the License at:
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
~ License for the specific language governing permissions and limitations
|
||||||
|
~ under the License.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-parent</artifactId>
|
||||||
|
<version>4.1.43.Final-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>netty-transport-blockhound-tests</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>
|
||||||
|
Tests for the BlockHound integration.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<name>Netty/Transport/BlockHound/Tests</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<skipJapicmp>true</skipJapicmp>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>netty-transport</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor.tools</groupId>
|
||||||
|
<artifactId>blockhound</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 The Netty Project
|
||||||
|
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.util.internal;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.blockhound.BlockHound;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class NettyBlockHoundIntegrationTest {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUpClass() {
|
||||||
|
BlockHound.install();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockingCallsInNettyThreads() throws Exception {
|
||||||
|
final FutureTask<Void> future = new FutureTask<>(() -> {
|
||||||
|
Thread.sleep(0);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
GlobalEventExecutor.INSTANCE.execute(future);
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.get(5, TimeUnit.SECONDS);
|
||||||
|
fail("Expected an exception due to a blocking call but none was thrown");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable throwable = e.getCause();
|
||||||
|
assertNotNull("An exception was thrown", throwable);
|
||||||
|
assertTrue("Blocking call was reported", throwable.getMessage().contains("Blocking call"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user