Skip to content

Commit a5d5af2

Browse files
authored
[grid] Using a single Netty client instance (#9227)
* [grid] Using a single Netty client instance This helps to reduce the amount of thread pools used to handle connections. This change is possibleTimeouts because timeouts can be set per request. Closes #9152
1 parent a65ff1c commit a5d5af2

File tree

4 files changed

+72
-40
lines changed

4 files changed

+72
-40
lines changed

java/client/src/org/openqa/selenium/remote/http/netty/NettyClient.java

+30-17
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
package org.openqa.selenium.remote.http.netty;
1919

2020
import com.google.auto.service.AutoService;
21-
import io.netty.util.HashedWheelTimer;
22-
import io.netty.util.Timer;
23-
import io.netty.util.concurrent.DefaultThreadFactory;
21+
2422
import org.asynchttpclient.AsyncHttpClient;
2523
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
2624
import org.asynchttpclient.Dsl;
@@ -35,14 +33,23 @@
3533
import org.openqa.selenium.remote.http.HttpResponse;
3634
import org.openqa.selenium.remote.http.WebSocket;
3735

36+
import io.netty.util.HashedWheelTimer;
37+
import io.netty.util.Timer;
38+
import io.netty.util.concurrent.DefaultThreadFactory;
39+
3840
import java.io.IOException;
3941
import java.io.UncheckedIOException;
4042
import java.util.concurrent.ThreadFactory;
4143
import java.util.concurrent.TimeUnit;
44+
import java.util.concurrent.atomic.AtomicBoolean;
4245
import java.util.function.BiFunction;
4346

4447
public class NettyClient implements HttpClient {
48+
4549
private static final Timer TIMER;
50+
private static final AtomicBoolean addedHook = new AtomicBoolean();
51+
private static final AsyncHttpClient client = createHttpClient(ClientConfig.defaultConfig());
52+
4653
static {
4754
ThreadFactory threadFactory = new DefaultThreadFactory("netty-client-timer", true);
4855
HashedWheelTimer timer = new HashedWheelTimer(
@@ -53,19 +60,30 @@ public class NettyClient implements HttpClient {
5360
timer.start();
5461
TIMER = timer;
5562
}
63+
5664
private final ClientConfig config;
57-
private final AsyncHttpClient client;
5865
private final HttpHandler handler;
5966
private final BiFunction<HttpRequest, WebSocket.Listener, WebSocket> toWebSocket;
6067

6168
private NettyClient(ClientConfig config) {
6269
this.config = Require.nonNull("HTTP client config", config);
63-
this.client = createHttpClient(config);
64-
this.handler = new NettyHttpHandler(config, this.client).with(config.filter());
65-
this.toWebSocket = NettyWebSocket.create(config, this.client);
70+
this.handler = new NettyHttpHandler(config, client).with(config.filter());
71+
this.toWebSocket = NettyWebSocket.create(config, client);
72+
if (!addedHook.get()) {
73+
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdownClient));
74+
addedHook.set(true);
75+
}
6676
}
6777

68-
private AsyncHttpClient createHttpClient(ClientConfig config) {
78+
/**
79+
* Converts a long to an int, clamping the maximum and minimum values to
80+
* fit in an integer without overflowing.
81+
*/
82+
static int toClampedInt(long value) {
83+
return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, value));
84+
}
85+
86+
private static AsyncHttpClient createHttpClient(ClientConfig config) {
6987
DefaultAsyncHttpClientConfig.Builder builder =
7088
new DefaultAsyncHttpClientConfig.Builder()
7189
.setThreadFactory(new DefaultThreadFactory("AsyncHttpClient", true))
@@ -77,18 +95,9 @@ private AsyncHttpClient createHttpClient(ClientConfig config) {
7795
.setRequestTimeout(toClampedInt(config.readTimeout().toMillis()))
7896
.setConnectTimeout(toClampedInt(config.connectionTimeout().toMillis()))
7997
.setReadTimeout(toClampedInt(config.readTimeout().toMillis()));
80-
8198
return Dsl.asyncHttpClient(builder);
8299
}
83100

84-
/**
85-
* Converts a long to an int, clamping the maximum and minimum values to
86-
* fit in an integer without overflowing.
87-
*/
88-
private int toClampedInt(long value) {
89-
return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, value));
90-
}
91-
92101
@Override
93102
public HttpResponse execute(HttpRequest request) {
94103
return handler.execute(request);
@@ -112,6 +121,10 @@ public HttpClient with(Filter filter) {
112121

113122
@Override
114123
public void close() {
124+
// no-op -- done by the shutdown hook
125+
}
126+
127+
private void shutdownClient() {
115128
try {
116129
client.close();
117130
} catch (IOException e) {

java/client/src/org/openqa/selenium/remote/http/netty/NettyHttpHandler.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.openqa.selenium.remote.http.netty;
1919

20+
import static org.openqa.selenium.remote.http.netty.NettyClient.toClampedInt;
21+
2022
import org.asynchttpclient.AsyncHttpClient;
2123
import org.asynchttpclient.Response;
2224
import org.openqa.selenium.internal.Require;
@@ -53,7 +55,11 @@ private HttpResponse makeCall(HttpRequest request) {
5355
Require.nonNull("Request", request);
5456

5557
Future<Response> whenResponse = client.executeRequest(
56-
NettyMessages.toNettyRequest(getConfig().baseUri(), request));
58+
NettyMessages.toNettyRequest(
59+
getConfig().baseUri(),
60+
toClampedInt(getConfig().readTimeout().toMillis()),
61+
toClampedInt(getConfig().readTimeout().toMillis()),
62+
request));
5763

5864
try {
5965
Response response = whenResponse.get(getConfig().readTimeout().toMillis(), TimeUnit.MILLISECONDS);

java/client/src/org/openqa/selenium/remote/http/netty/NettyMessages.java

+26-20
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
package org.openqa.selenium.remote.http.netty;
1919

20+
import static org.asynchttpclient.Dsl.request;
2021
import static org.openqa.selenium.remote.http.Contents.empty;
22+
import static org.openqa.selenium.remote.http.Contents.memoize;
23+
24+
import com.google.common.base.Strings;
2125

2226
import org.asynchttpclient.Dsl;
2327
import org.asynchttpclient.Request;
@@ -30,32 +34,20 @@
3034

3135
import java.net.URI;
3236

33-
import static org.asynchttpclient.Dsl.request;
34-
import static org.openqa.selenium.remote.http.Contents.memoize;
35-
36-
import com.google.common.base.Strings;
37-
3837
class NettyMessages {
3938

4039
private NettyMessages() {
4140
// Utility classes.
4241
}
4342

44-
protected static Request toNettyRequest(URI baseUrl, HttpRequest request) {
45-
String rawUrl;
43+
protected static Request toNettyRequest(URI baseUrl, int readTimeout, int requestTimeout,
44+
HttpRequest request) {
4645

47-
String uri = request.getUri();
48-
if (uri.startsWith("ws://")) {
49-
rawUrl = "http://" + uri.substring("ws://".length());
50-
} else if (uri.startsWith("wss://")) {
51-
rawUrl = "https://" + uri.substring("wss://".length());
52-
} else if (uri.startsWith("http://") || uri.startsWith("https://")) {
53-
rawUrl = uri;
54-
} else {
55-
rawUrl = baseUrl.toString().replaceAll("/$", "") + uri;
56-
}
46+
String rawUrl = getRawUrl(baseUrl, request.getUri());
5747

58-
RequestBuilder builder = request(request.getMethod().toString(), rawUrl);
48+
RequestBuilder builder = request(request.getMethod().toString(), rawUrl)
49+
.setReadTimeout(readTimeout)
50+
.setRequestTimeout(requestTimeout);
5951

6052
for (String name : request.getQueryParameterNames()) {
6153
for (String value : request.getQueryParameters(name)) {
@@ -93,13 +85,27 @@ public static HttpResponse toSeleniumResponse(Response response) {
9385

9486
toReturn.setStatus(response.getStatusCode());
9587

96-
toReturn.setContent(! response.hasResponseBody()
88+
toReturn.setContent(!response.hasResponseBody()
9789
? empty()
9890
: memoize(response::getResponseBodyAsStream));
9991

10092
response.getHeaders().names().forEach(
101-
name -> response.getHeaders(name).forEach(value -> toReturn.addHeader(name, value)));
93+
name -> response.getHeaders(name).forEach(value -> toReturn.addHeader(name, value)));
10294

10395
return toReturn;
10496
}
97+
98+
private static String getRawUrl(URI baseUrl, String uri) {
99+
String rawUrl;
100+
if (uri.startsWith("ws://")) {
101+
rawUrl = "http://" + uri.substring("ws://".length());
102+
} else if (uri.startsWith("wss://")) {
103+
rawUrl = "https://" + uri.substring("wss://".length());
104+
} else if (uri.startsWith("http://") || uri.startsWith("https://")) {
105+
rawUrl = uri;
106+
} else {
107+
rawUrl = baseUrl.toString().replaceAll("/$", "") + uri;
108+
}
109+
return rawUrl;
110+
}
105111
}

java/client/src/org/openqa/selenium/remote/http/netty/NettyWebSocket.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
package org.openqa.selenium.remote.http.netty;
1919

20+
import static org.openqa.selenium.remote.http.netty.NettyClient.toClampedInt;
21+
2022
import org.asynchttpclient.AsyncHttpClient;
2123
import org.asynchttpclient.ListenableFuture;
24+
import org.asynchttpclient.Request;
2225
import org.asynchttpclient.ws.WebSocketListener;
2326
import org.asynchttpclient.ws.WebSocketUpgradeHandler;
2427
import org.openqa.selenium.internal.Require;
@@ -50,7 +53,7 @@ class NettyWebSocket implements WebSocket {
5053

5154
private final org.asynchttpclient.ws.WebSocket socket;
5255

53-
private NettyWebSocket(AsyncHttpClient client, org.asynchttpclient.Request request, Listener listener) {
56+
private NettyWebSocket(AsyncHttpClient client, Request request, Listener listener) {
5457
Require.nonNull("HTTP client", client);
5558
Require.nonNull("WebSocket listener", listener);
5659

@@ -124,7 +127,11 @@ static BiFunction<HttpRequest, Listener, WebSocket> create(ClientConfig config,
124127
return (req, listener) -> {
125128
HttpRequest filtered = filterRequest.apply(req);
126129

127-
org.asynchttpclient.Request nettyReq = NettyMessages.toNettyRequest(config.baseUri(), filtered);
130+
Request nettyReq = NettyMessages.toNettyRequest(
131+
config.baseUri(),
132+
toClampedInt(config.readTimeout().toMillis()),
133+
toClampedInt(config.readTimeout().toMillis()),
134+
filtered);
128135

129136
return new NettyWebSocket(client, nettyReq, listener);
130137
};

0 commit comments

Comments
 (0)