Repository: netty/netty
Issue: Unexpected WebClient blocks reported by BlockHound

I'm new to webflux so I posted a [question on stackoverflow](https://stackoverflow.com/questions/66091050/why-is-webclient-blocking-in-this-web-flux-app) thinking I was doing something wrong in my code, but after seeing [this issue](https://github.com/netty/netty/issues/10925) and related [pull request](https://github.com/netty/netty/pull/10935), I'm thinking I just need to tell BlockHound to ignore some calls. 

I created [my own BlockHoundIntegration](https://github.com/aztosca/web-client-test/blob/master/src/main/java/org/app/webclienttest/MyBlockHoundIntegration.java) that suppresses the BlockHound exceptions when added to the BlockHound.install() call, but wanted to raise an issue here to see if maybe this is something that should be addressed in NettyBlockHoundIntegration.

### Expected behavior
No blocking calls reported by BlockHound
### Actual behavior
Blocking calls reported by BlockHound
### Steps to reproduce

1. Run the simple boot app linked below in the _reproducer code_ section.
2. target http://localhost:8080/test-web-client

### Minimal yet complete reproducer code (or URL to code)
https://github.com/aztosca/web-client-test
https://github.com/aztosca/web-client-test/blob/master/src/main/java/org/app/webclienttest/WebClientTestApplication.java
### Netty version
4.1.58
### JVM version (e.g. `java -version`)
openjdk version "11.0.2"
### OS version (e.g. `uname -a`)
MacOS 11.2

Issue comments:
Thanks @violetagg . [MyBlockHoundIntegration](https://github.com/aztosca/web-client-test/blob/master/src/main/java/org/app/webclienttest/MyBlockHoundIntegration.java) had another entry that allows reading the truststore. Was there any reason not to include this (or similar) in the PR? I understand some things shouldn't be allowed by default and maybe folks need to make up their on mind on this one.

I did some testing with WebClient and can see that the blocking call only happens once on the first http**s** endpoint WebClient calls so it seems like I can safely allow it.

For quick reference of the BlockHound setting and blocking call it allows...

```
        builder.allowBlockingCallsInside(
        		"io.netty.handler.ssl.SslContext",
        		"newClientContextInternal");
```

```
Caused by: reactor.blockhound.BlockingOperationError: Blocking call! java.io.FileInputStream#readBytes
	at java.base/java.io.FileInputStream.readBytes(FileInputStream.java)
	at java.base/java.io.FileInputStream.read(FileInputStream.java:279)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:271)
	at java.base/sun.security.util.DerValue.init(DerValue.java:388)
	at java.base/sun.security.util.DerValue.<init>(DerValue.java:331)
	at java.base/sun.security.util.DerValue.<init>(DerValue.java:344)
	at java.base/sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1993)
	at java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
	at java.base/java.security.KeyStore.load(KeyStore.java:1479)
	at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.loadKeyStore(TrustStoreManager.java:365)
	at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.getTrustedCerts(TrustStoreManager.java:313)
	at java.base/sun.security.ssl.TrustStoreManager.getTrustedCerts(TrustStoreManager.java:55)
	at java.base/sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:49)
	at java.base/javax.net.ssl.TrustManagerFactory.init(TrustManagerFactory.java:278)
	at java.base/sun.security.ssl.SSLContextImpl.engineInit(SSLContextImpl.java:88)
	at java.base/javax.net.ssl.SSLContext.init(SSLContext.java:297)
	at io.netty.handler.ssl.JdkSslContext.<clinit>(JdkSslContext.java:75)
	at io.netty.handler.ssl.SslContext.newClientContextInternal(SslContext.java:821)
	at io.netty.handler.ssl.SslContextBuilder.build(SslContextBuilder.java:577)
        ......
```
@aztosca In the example, I do not see the SslContext customisation. https://github.com/aztosca/web-client-test/blob/master/src/main/java/org/app/webclienttest/WebClientTestApplication.java

Do you use the default security in WebClient?
Yeah, default. The injected WebClient.Builder I'm using is spring's pre-configured one, and I'm not using any other ssl related command line settings when running the app. 

Pull request review comments:

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/Hidden.java b/common/src/main/java/io/netty/util/internal/Hidden.java
index 18a03c68ead..fab714d8ad6 100644
--- a/common/src/main/java/io/netty/util/internal/Hidden.java
+++ b/common/src/main/java/io/netty/util/internal/Hidden.java
@@ -119,6 +119,18 @@ public void applyTo(BlockHound.Builder builder) {
                     "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider",
                     "parse");
 
+            builder.allowBlockingCallsInside(
+                    "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider",
+                    "parseEtcResolverSearchDomains");
+
+            builder.allowBlockingCallsInside(
+                    "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider",
+                    "parseEtcResolverOptions");
+
+            builder.allowBlockingCallsInside(
+                    "io.netty.resolver.HostsFileParser",
+                    "parse");
+
             builder.nonBlockingThreadPredicate(new Function<Predicate<Thread>, Predicate<Thread>>() {
                 @Override
                 public Predicate<Thread> apply(final Predicate<Thread> p) {
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
index 84412c58ec3..d6b3875c713 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
@@ -86,7 +86,7 @@ static DnsServerAddressStreamProvider parseSilently() {
      * the default DNS server to use, and also overrides for individual domains. Also parse list of files of the format
      * <a href="
      * https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
-     * /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
+     * /etc/resolver</a> which may contain multiple files to override the name servers used for multiple domains.
      * @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
      * @param etcResolverFiles List of files of the format defined in
      * <a href="
@@ -121,7 +121,7 @@ public UnixResolverDnsServerAddressStreamProvider(File etcResolvConf, File... et
      * the default DNS server to use, and also overrides for individual domains. Also parse a directory of the format
      * <a href="
      * https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
-     * /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
+     * /etc/resolver</a> which may contain multiple files to override the name servers used for multiple domains.
      * @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
      * @param etcResolverDir Directory containing files of the format defined in
      * <a href="
@@ -379,7 +379,7 @@ static List<String> parseEtcResolverSearchDomains(File etcResolvConf) throws IOE
                 } else if (line.startsWith(SEARCH_ROW_LABEL)) {
                     int i = indexOfNonWhiteSpace(line, SEARCH_ROW_LABEL.length());
                     if (i >= 0) {
-                        // May contain more then one entry, either seperated by whitespace or tab.
+                        // May contain more then one entry, either separated by whitespace or tab.
                         // See https://linux.die.net/man/5/resolver
                         String[] domains = WHITESPACE_PATTERN.split(line.substring(i));
                         Collections.addAll(searchDomains, domains);


Test patch:
diff --git a/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java b/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java
index 2d5121a0781..1b454a1802e 100644
--- a/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java
+++ b/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java
@@ -26,6 +26,7 @@
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioDatagramChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import io.netty.handler.ssl.SslContext;
@@ -35,7 +36,7 @@
 import io.netty.handler.ssl.SslProvider;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
 import io.netty.handler.ssl.util.SelfSignedCertificate;
-import io.netty.resolver.dns.DnsServerAddressStreamProvider;
+import io.netty.resolver.dns.DnsNameResolverBuilder;
 import io.netty.resolver.dns.DnsServerAddressStreamProviders;
 import io.netty.util.HashedWheelTimer;
 import io.netty.util.ReferenceCountUtil;
@@ -59,6 +60,7 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.ServiceLoader;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -320,7 +322,30 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
     }
 
     @Test(timeout = 5000L)
-    public void testParseEtcResolverFilesAllowsBlockingCalls() throws InterruptedException {
+    public void testUnixResolverDnsServerAddressStreamProvider_Parse() throws InterruptedException {
+        doTestParseResolverFilesAllowsBlockingCalls(DnsServerAddressStreamProviders::unixDefault);
+    }
+
+    @Test(timeout = 5000L)
+    public void testHostsFileParser_Parse() throws InterruptedException {
+        doTestParseResolverFilesAllowsBlockingCalls(DnsNameResolverBuilder::new);
+    }
+
+    @Test(timeout = 5000L)
+    public void testUnixResolverDnsServerAddressStreamProvider_ParseEtcResolverSearchDomainsAndOptions()
+            throws InterruptedException {
+        NioEventLoopGroup group = new NioEventLoopGroup();
+        try {
+            DnsNameResolverBuilder builder = new DnsNameResolverBuilder(group.next())
+                    .channelFactory(NioDatagramChannel::new);
+            doTestParseResolverFilesAllowsBlockingCalls(builder::build);
+        } finally {
+            group.shutdownGracefully();
+        }
+    }
+
+    private static void doTestParseResolverFilesAllowsBlockingCalls(Callable<Object> callable)
+            throws InterruptedException {
         SingleThreadEventExecutor executor =
                 new SingleThreadEventExecutor(null, new DefaultThreadFactory("test"), true) {
                     @Override
@@ -335,11 +360,11 @@ protected void run() {
                 };
         try {
             CountDownLatch latch = new CountDownLatch(1);
-            List<DnsServerAddressStreamProvider> result = new ArrayList<>();
+            List<Object> result = new ArrayList<>();
             List<Throwable> error = new ArrayList<>();
             executor.execute(() -> {
                 try {
-                    result.add(DnsServerAddressStreamProviders.unixDefault());
+                    result.add(callable.call());
                 } catch (Throwable t) {
                     error.add(t);
                 }