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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<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-shading</module>
|
||||
<module>testsuite-native-image</module>
|
||||
<module>transport-blockhound-tests</module>
|
||||
<module>microbench</module>
|
||||
<module>bom</module>
|
||||
</modules>
|
||||
@ -674,6 +675,13 @@
|
||||
<version>${log4j2.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- BlockHound integration -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<version>1.0.1.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</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