jersey icon indicating copy to clipboard operation
jersey copied to clipboard

NettyConnector hangs indefinitely when there is no server listening on port

Open adriansuarez opened this issue 1 year ago • 0 comments

Observed behavior

When connecting to a port that has no server is listening on it, the NettyConnector provider for the Jersey client hangs indefinitely. This seems to be a regression introduced between versions 3.0.8 and 3.1.2.

This is extremely straightforward to reproduce and does not seem to be a timing issue, because it happens every single time I issue an HTTP request without a server running.

Test case

The following code reproduces the issue with version 3.1.2 of Jersey (I'm using version 4.1.94.Final of Netty):

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.netty.connector.NettyConnectorProvider;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;

public class NettyProviderHang {

    public static void main(String... args) {
        // configure NettyConnectionProvider and create client
        ClientConfig config = new ClientConfig();
        config.connectorProvider(new NettyConnectorProvider());
        Client client = ClientBuilder.newClient(config);
        try {
            // issue HTTP request
            WebTarget target = client.target("http://localhost:8080");
            target.request().get();
        } finally {
            client.close();
        }
    }
}

This is the stacktrace for the hung thread:

Thread[main,5,main]
	[email protected]/jdk.internal.misc.Unsafe.park(Native Method)
	[email protected]/java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
	[email protected]/java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1864)
	[email protected]/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3465)
	[email protected]/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3436)
	[email protected]/java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1898)
	[email protected]/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2117)
	app//org.glassfish.jersey.netty.connector.NettyConnector.apply(NettyConnector.java:166)
	app//org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:300)
	app//org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662)
	app//org.glassfish.jersey.client.JerseyInvocation$$Lambda$207/0x000000014319c628.call(Unknown Source)
	app//org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
	app//org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691)
	app//org.glassfish.jersey.client.JerseyInvocation$$Lambda$208/0x000000014319c868.call(Unknown Source)
	app//org.glassfish.jersey.internal.Errors.process(Errors.java:292)
	app//org.glassfish.jersey.internal.Errors.process(Errors.java:274)
	app//org.glassfish.jersey.internal.Errors.process(Errors.java:205)
	app//org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
	app//org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691)
	app//org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:661)
	app//org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:413)
	app//org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:313)
	app//NettyProviderHang.main(NettyProviderHang.java:33)

Expected behavior

I would expect the client to fail immediately with a "Connection refused" error message. This is what happens on version 3.0.8 with the Netty connector (same version of Netty):

Exception in thread "main" jakarta.ws.rs.ProcessingException: Connection refused: localhost/127.0.0.1:8080
	at org.glassfish.jersey.netty.connector.NettyConnector.apply(NettyConnector.java:170)
	at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297)
	at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662)
	at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
	at org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
	at org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691)
	at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:661)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:413)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:313)
	at NettyProviderHang.main(NettyProviderHang.java:18)
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8080
Caused by: java.net.ConnectException: Connection refused
	at java.base/sun.nio.ch.Net.pollConnect(Native Method)
	at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
	at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:946)
	at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:337)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:776)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)

When using version 3.1.2 of Jersey without the NettyConnector (commenting out config.connectorProvider() invocation) we also get the expected behavior:

Exception in thread "main" jakarta.ws.rs.ProcessingException: java.net.ConnectException: Connection refused
	at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:275)
	at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:300)
	at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662)
	at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
	at org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
	at org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691)
	at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:661)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:413)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:313)
	at NettyProviderHang.main(NettyProviderHang.java:17)
Caused by: java.net.ConnectException: Connection refused
	at java.base/sun.nio.ch.Net.connect0(Native Method)
	at java.base/sun.nio.ch.Net.connect(Net.java:579)
	at java.base/sun.nio.ch.Net.connect(Net.java:568)
	at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:593)
	at java.base/java.net.Socket.connect(Socket.java:633)
	at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
	at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:533)
	at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:638)
	at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:281)
	at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:386)
	at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:408)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1309)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1242)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1128)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1057)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1665)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)
	at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:409)
	at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:273)
	... 13 more

Environment

Java 17 Jersey 3.1.2 Netty 4.1.94.Final

adriansuarez avatar Jul 21 '23 03:07 adriansuarez