Skip to content

Commit b0c1470

Browse files
committed
Merge pull request AsyncHttpClient#954 from stepancheg/name
AsyncHttpClient `name` option
2 parents b5613c9 + a2521fa commit b0c1470

File tree

10 files changed

+249
-7
lines changed

10 files changed

+249
-7
lines changed

api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.asynchttpclient.filter.ResponseFilter;
3535
import org.asynchttpclient.proxy.ProxyServer;
3636
import org.asynchttpclient.proxy.ProxyServerSelector;
37+
import org.asynchttpclient.util.PrefixIncrementThreadFactory;
3738
import org.asynchttpclient.util.ProxyUtils;
3839

3940
/**
@@ -66,6 +67,8 @@ public class AsyncHttpClientConfig {
6667
AHC_VERSION = prop.getProperty("ahc.version", "UNKNOWN");
6768
}
6869

70+
protected String name;
71+
6972
protected int connectTimeout;
7073

7174
protected int maxConnections;
@@ -118,7 +121,8 @@ public class AsyncHttpClientConfig {
118121
protected AsyncHttpClientConfig() {
119122
}
120123

121-
private AsyncHttpClientConfig(int connectTimeout,//
124+
private AsyncHttpClientConfig(String name,//
125+
int connectTimeout,//
122126
int maxConnections,//
123127
int maxConnectionsPerHost,//
124128
int requestTimeout,//
@@ -160,6 +164,7 @@ private AsyncHttpClientConfig(int connectTimeout,//
160164
boolean keepEncodingHeader,//
161165
AsyncHttpProviderConfig<?, ?> providerConfig) {
162166

167+
this.name = name;
163168
this.connectTimeout = connectTimeout;
164169
this.maxConnections = maxConnections;
165170
this.maxConnectionsPerHost = maxConnectionsPerHost;
@@ -178,7 +183,15 @@ private AsyncHttpClientConfig(int connectTimeout,//
178183
this.proxyServerSelector = proxyServerSelector;
179184
this.compressionEnforced = compressionEnforced;
180185
this.userAgent = userAgent;
181-
this.applicationThreadPool = applicationThreadPool == null ? Executors.newCachedThreadPool() : applicationThreadPool;
186+
187+
if (applicationThreadPool != null) {
188+
this.applicationThreadPool = applicationThreadPool;
189+
} else {
190+
PrefixIncrementThreadFactory threadFactory = new PrefixIncrementThreadFactory(
191+
getNameOrDefault() + "-");
192+
this.applicationThreadPool = Executors.newCachedThreadPool(threadFactory);
193+
}
194+
182195
this.realm = realm;
183196
this.requestFilters = requestFilters;
184197
this.responseFilters = responseFilters;
@@ -203,6 +216,32 @@ private AsyncHttpClientConfig(int connectTimeout,//
203216
this.keepEncodingHeader = keepEncodingHeader;
204217
}
205218

219+
/**
220+
* Return the name of {@link AsyncHttpClient}, which is used for thread naming
221+
* and debugging.
222+
*
223+
* @return the name.
224+
*/
225+
public String getName() {
226+
return name;
227+
}
228+
229+
/**
230+
* Return the name of {@link AsyncHttpClient}, or default string if name is null or empty.
231+
*
232+
* @return the name.
233+
*/
234+
public String getNameOrDefault() {
235+
String r = name;
236+
if (r == null || r.isEmpty()) {
237+
r = defaultName();
238+
}
239+
if (r == null || r.isEmpty()) {
240+
r = "AsyncHttpClient";
241+
}
242+
return r;
243+
}
244+
206245
/**
207246
* Return the maximum number of connections an {@link AsyncHttpClient} can
208247
* handle.
@@ -577,6 +616,7 @@ public boolean isKeepEncodingHeader() {
577616
* Builder for an {@link AsyncHttpClient}
578617
*/
579618
public static class Builder {
619+
private String name = defaultName();
580620
private int connectTimeout = defaultConnectTimeout();
581621
private int maxConnections = defaultMaxConnections();
582622
private int maxConnectionsPerHost = defaultMaxConnectionsPerHost();
@@ -624,6 +664,16 @@ public static class Builder {
624664
public Builder() {
625665
}
626666

667+
/**
668+
* Set the name of {@link AsyncHttpClient}. That name is used for thread
669+
* naming and can be used for debugging multiple {@link AsyncHttpClient}
670+
* instance.
671+
*/
672+
public Builder setName(String name) {
673+
this.name = name;
674+
return this;
675+
}
676+
627677
/**
628678
* Set the maximum number of connections an {@link AsyncHttpClient} can
629679
* handle.
@@ -1113,6 +1163,7 @@ public Builder setKeepEncodingHeader(boolean keepEncodingHeader) {
11131163
* @param prototype the configuration to use as a prototype.
11141164
*/
11151165
public Builder(AsyncHttpClientConfig prototype) {
1166+
name = prototype.getName();
11161167
allowPoolingConnections = prototype.isAllowPoolingConnections();
11171168
connectTimeout = prototype.getConnectTimeout();
11181169
pooledConnectionIdleTimeout = prototype.getPooledConnectionIdleTimeout();
@@ -1178,7 +1229,8 @@ public AsyncHttpClientConfig build() {
11781229
if (proxyServerSelector == null)
11791230
proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR;
11801231

1181-
return new AsyncHttpClientConfig(connectTimeout,//
1232+
return new AsyncHttpClientConfig(name,//
1233+
connectTimeout,//
11821234
maxConnections,//
11831235
maxConnectionsPerHost,//
11841236
requestTimeout,//

api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigBean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ void configureFilters() {
5151
void configureDefaults() {
5252
maxConnections = defaultMaxConnections();
5353
maxConnectionsPerHost = defaultMaxConnectionsPerHost();
54+
name = defaultName();
5455
connectTimeout = defaultConnectTimeout();
5556
webSocketTimeout = defaultWebSocketTimeout();
5657
pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout();
@@ -88,6 +89,11 @@ public Thread newThread(Runnable r) {
8889
});
8990
}
9091

92+
public AsyncHttpClientConfigBean setName(String name) {
93+
this.name = name;
94+
return this;
95+
}
96+
9197
public AsyncHttpClientConfigBean setMaxTotalConnections(int maxConnections) {
9298
this.maxConnections = maxConnections;
9399
return this;

api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ private AsyncHttpClientConfigDefaults() {
1919

2020
public static final String ASYNC_CLIENT_CONFIG_ROOT = "org.asynchttpclient.";
2121

22+
public static String defaultName() {
23+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(ASYNC_CLIENT_CONFIG_ROOT + "name");
24+
}
25+
2226
public static int defaultMaxConnections() {
2327
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "maxConnections");
2428
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.util;
15+
16+
import java.util.concurrent.ThreadFactory;
17+
import java.util.concurrent.atomic.AtomicInteger;
18+
19+
/**
20+
* Thread factory that generates thread names by adding incrementing number
21+
* to the specified prefix.
22+
*
23+
* @author Stepan Koltsov
24+
*/
25+
public class PrefixIncrementThreadFactory implements ThreadFactory {
26+
private final String namePrefix;
27+
private final AtomicInteger threadNumber = new AtomicInteger();
28+
29+
public PrefixIncrementThreadFactory(String namePrefix) {
30+
if (namePrefix == null || namePrefix.isEmpty()) {
31+
throw new IllegalArgumentException("namePrefix must not be empty");
32+
}
33+
this.namePrefix = namePrefix;
34+
}
35+
36+
public Thread newThread(Runnable r) {
37+
return new Thread(r, namePrefix + threadNumber.incrementAndGet());
38+
}
39+
}

api/src/main/resources/ahc-default.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
org.asynchttpclient.name=AsyncHttpClient
12
org.asynchttpclient.maxConnections=-1
23
org.asynchttpclient.maxConnectionsPerHost=-1
34
org.asynchttpclient.connectTimeout=5000
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient;
15+
16+
import java.util.Arrays;
17+
import java.util.Random;
18+
import java.util.concurrent.Future;
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.testng.Assert;
22+
import org.testng.annotations.Test;
23+
24+
/**
25+
* Tests configured client name is used for thread names.
26+
*
27+
* @author Stepan Koltsov
28+
*/
29+
public abstract class ThreadNameTest extends AbstractBasicTest {
30+
31+
private static Thread[] getThreads() {
32+
int count = Thread.activeCount() + 1;
33+
for (;;) {
34+
Thread[] threads = new Thread[count];
35+
int filled = Thread.enumerate(threads);
36+
if (filled < threads.length) {
37+
return Arrays.copyOf(threads, filled);
38+
}
39+
40+
count *= 2;
41+
}
42+
}
43+
44+
@Test(groups = { "standalone", "default_provider" })
45+
public void testQueryParameters() throws Exception {
46+
String name = "ahc-" + (new Random().nextLong() & 0x7fffffffffffffffL);
47+
AsyncHttpClientConfig.Builder config = new AsyncHttpClientConfig.Builder();
48+
config.setName(name);
49+
try (AsyncHttpClient client = getAsyncHttpClient(config.build())) {
50+
Future<Response> f = client.prepareGet("http://127.0.0.1:" + port1 + "/").execute();
51+
f.get(3, TimeUnit.SECONDS);
52+
53+
// We cannot assert that all threads are created with specified name,
54+
// so we checking that at least one thread is.
55+
boolean found = false;
56+
for (Thread thread : getThreads()) {
57+
if (thread.getName().startsWith(name)) {
58+
found = true;
59+
break;
60+
}
61+
}
62+
63+
Assert.assertTrue(found, "must found threads starting with random string " + name);
64+
}
65+
}
66+
}

providers/netty3/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.concurrent.ExecutorService;
2929
import java.util.concurrent.Executors;
3030
import java.util.concurrent.Semaphore;
31+
import java.util.concurrent.ThreadFactory;
3132
import java.util.concurrent.atomic.AtomicBoolean;
3233

3334
import javax.net.ssl.SSLEngine;
@@ -52,21 +53,26 @@
5253
import org.asynchttpclient.netty.request.NettyRequestSender;
5354
import org.asynchttpclient.proxy.ProxyServer;
5455
import org.asynchttpclient.uri.Uri;
56+
import org.asynchttpclient.util.PrefixIncrementThreadFactory;
5557
import org.jboss.netty.bootstrap.ClientBootstrap;
5658
import org.jboss.netty.channel.Channel;
5759
import org.jboss.netty.channel.ChannelPipeline;
5860
import org.jboss.netty.channel.ChannelPipelineFactory;
5961
import org.jboss.netty.channel.DefaultChannelFuture;
6062
import org.jboss.netty.channel.group.ChannelGroup;
6163
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
64+
import org.jboss.netty.channel.socket.nio.NioClientBossPool;
6265
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
66+
import org.jboss.netty.channel.socket.nio.NioWorkerPool;
6367
import org.jboss.netty.handler.codec.http.HttpClientCodec;
6468
import org.jboss.netty.handler.codec.http.HttpContentDecompressor;
6569
import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder;
6670
import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder;
6771
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
6872
import org.jboss.netty.handler.ssl.SslHandler;
6973
import org.jboss.netty.handler.stream.ChunkedWriteHandler;
74+
import org.jboss.netty.util.HashedWheelTimer;
75+
import org.jboss.netty.util.ThreadNameDeterminer;
7076
import org.jboss.netty.util.Timer;
7177
import org.slf4j.Logger;
7278
import org.slf4j.LoggerFactory;
@@ -181,11 +187,16 @@ public Semaphore apply(Object partitionKey) {
181187

182188
} else {
183189
ExecutorService e = nettyConfig.getBossExecutorService();
184-
if (e == null)
185-
e = Executors.newCachedThreadPool();
190+
if (e == null) {
191+
ThreadFactory threadFactory = new PrefixIncrementThreadFactory(
192+
config.getNameOrDefault() + "-boss-");
193+
e = Executors.newCachedThreadPool(threadFactory);
194+
}
186195
int numWorkers = config.getIoThreadMultiplier() * Runtime.getRuntime().availableProcessors();
187196
LOGGER.trace("Number of application's worker threads is {}", numWorkers);
188-
socketChannelFactory = new NioClientSocketChannelFactory(e, config.executorService(), numWorkers);
197+
NioClientBossPool nioClientBossPool = new NioClientBossPool(e, 1, new HashedWheelTimer(), ThreadNameDeterminer.CURRENT);
198+
NioWorkerPool nioWorkerPool = new NioWorkerPool(config.executorService(), numWorkers, ThreadNameDeterminer.CURRENT);
199+
socketChannelFactory = new NioClientSocketChannelFactory(nioClientBossPool, nioWorkerPool);
189200
allowReleaseSocketChannelFactory = true;
190201
}
191202

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.netty;
15+
16+
import org.asynchttpclient.AsyncHttpClient;
17+
import org.asynchttpclient.AsyncHttpClientConfig;
18+
import org.asynchttpclient.RetryRequestTest;
19+
import org.asynchttpclient.ThreadNameTest;
20+
21+
/**
22+
* @author Stepan Koltsov
23+
*/
24+
public class NettyThreadNameTest extends ThreadNameTest {
25+
@Override
26+
public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) {
27+
return NettyProviderUtil.nettyProvider(config);
28+
}
29+
}

providers/netty4/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.netty.handler.ssl.SslHandler;
3939
import io.netty.handler.stream.ChunkedWriteHandler;
4040
import io.netty.util.Timer;
41+
import io.netty.util.concurrent.DefaultThreadFactory;
4142
import io.netty.util.internal.chmv8.ConcurrentHashMapV8;
4243

4344
import java.io.IOException;
@@ -172,7 +173,12 @@ public Semaphore apply(Object partitionKey) {
172173

173174
// check if external EventLoopGroup is defined
174175
allowReleaseEventLoopGroup = nettyConfig.getEventLoopGroup() == null;
175-
eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyConfig.getEventLoopGroup();
176+
if (allowReleaseEventLoopGroup) {
177+
DefaultThreadFactory threadFactory = new DefaultThreadFactory(config.getNameOrDefault());
178+
eventLoopGroup = new NioEventLoopGroup(0, threadFactory);
179+
} else {
180+
eventLoopGroup = nettyConfig.getEventLoopGroup();
181+
}
176182
if (eventLoopGroup instanceof OioEventLoopGroup)
177183
throw new IllegalArgumentException("Oio is not supported");
178184

0 commit comments

Comments
 (0)