Skip to content

Commit f045f40

Browse files
committed
Start request timeout before DNS and connect, close AsyncHttpClient#1060
1 parent 83bdfbf commit f045f40

File tree

7 files changed

+102
-52
lines changed

7 files changed

+102
-52
lines changed

client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ public int incrementAndGetCurrentRedirectCount() {
325325
public void setTimeoutsHolder(TimeoutsHolder timeoutsHolder) {
326326
this.timeoutsHolder = timeoutsHolder;
327327
}
328+
329+
public TimeoutsHolder getTimeoutsHolder() {
330+
return timeoutsHolder;
331+
}
328332

329333
public AtomicBoolean getInAuth() {
330334
return inAuth;

client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import static org.asynchttpclient.util.Assertions.assertNotNull;
1717
import static org.asynchttpclient.util.AuthenticatorUtils.*;
1818
import static org.asynchttpclient.util.HttpConstants.Methods.*;
19-
import static org.asynchttpclient.util.HttpUtils.requestTimeout;
2019
import static org.asynchttpclient.util.MiscUtils.getCause;
2120
import static org.asynchttpclient.util.ProxyUtils.getProxyServer;
2221
import io.netty.bootstrap.Bootstrap;
@@ -27,14 +26,11 @@
2726
import io.netty.handler.codec.http.HttpHeaders;
2827
import io.netty.handler.codec.http.HttpMethod;
2928
import io.netty.handler.codec.http.HttpRequest;
30-
import io.netty.util.Timeout;
3129
import io.netty.util.Timer;
32-
import io.netty.util.TimerTask;
3330

3431
import java.io.IOException;
3532
import java.net.InetSocketAddress;
3633
import java.util.List;
37-
import java.util.concurrent.TimeUnit;
3834
import java.util.concurrent.atomic.AtomicBoolean;
3935

4036
import org.asynchttpclient.AsyncHandler;
@@ -56,8 +52,6 @@
5652
import org.asynchttpclient.netty.channel.ChannelState;
5753
import org.asynchttpclient.netty.channel.Channels;
5854
import org.asynchttpclient.netty.channel.NettyConnectListener;
59-
import org.asynchttpclient.netty.timeout.ReadTimeoutTimerTask;
60-
import org.asynchttpclient.netty.timeout.RequestTimeoutTimerTask;
6155
import org.asynchttpclient.netty.timeout.TimeoutsHolder;
6256
import org.asynchttpclient.proxy.ProxyServer;
6357
import org.asynchttpclient.resolver.RequestHostnameResolver;
@@ -218,6 +212,7 @@ private <T> ListenableFuture<T> sendRequestWithOpenChannel(Request request, Prox
218212
if (asyncHandler instanceof AsyncHandlerExtensions)
219213
AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPooled(channel);
220214

215+
scheduleRequestTimeout(future);
221216
future.setChannelState(ChannelState.POOLED);
222217
future.attachChannel(channel, false);
223218

@@ -274,6 +269,8 @@ private <T> ListenableFuture<T> sendRequestWithNewChannel(//
274269
return future;
275270
}
276271

272+
scheduleRequestTimeout(future);
273+
277274
RequestHostnameResolver.INSTANCE.resolve(request, proxy, asyncHandler)//
278275
.addListener(new SimpleFutureListener<List<InetSocketAddress>>() {
279276

@@ -341,9 +338,9 @@ public <T> void writeRequest(NettyResponseFuture<T> future, Channel channel) {
341338
if (writeBody)
342339
nettyRequest.getBody().write(channel, future);
343340

344-
// don't bother scheduling timeouts if channel became invalid
341+
// don't bother scheduling read timeout if channel became invalid
345342
if (Channels.isChannelValid(channel))
346-
scheduleTimeouts(future);
343+
scheduleReadTimeout(future);
347344

348345
} catch (Exception e) {
349346
LOGGER.error("Can't write request", e);
@@ -356,27 +353,16 @@ private void configureTransferAdapter(AsyncHandler<?> handler, HttpRequest httpR
356353
TransferCompletionHandler.class.cast(handler).headers(h);
357354
}
358355

359-
private void scheduleTimeouts(NettyResponseFuture<?> nettyResponseFuture) {
360-
356+
private void scheduleRequestTimeout(NettyResponseFuture<?> nettyResponseFuture) {
361357
nettyResponseFuture.touch();
362-
int requestTimeoutInMs = requestTimeout(config, nettyResponseFuture.getTargetRequest());
363-
TimeoutsHolder timeoutsHolder = new TimeoutsHolder();
364-
if (requestTimeoutInMs != -1) {
365-
Timeout requestTimeout = newTimeout(new RequestTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs), requestTimeoutInMs);
366-
timeoutsHolder.requestTimeout = requestTimeout;
367-
}
368-
369-
int readTimeoutValue = config.getReadTimeout();
370-
if (readTimeoutValue != -1 && readTimeoutValue < requestTimeoutInMs) {
371-
// no need to schedule a readTimeout if the requestTimeout happens first
372-
Timeout readTimeout = newTimeout(new ReadTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs, readTimeoutValue), readTimeoutValue);
373-
timeoutsHolder.readTimeout = readTimeout;
374-
}
358+
TimeoutsHolder timeoutsHolder = new TimeoutsHolder(nettyTimer, nettyResponseFuture, this, config);
375359
nettyResponseFuture.setTimeoutsHolder(timeoutsHolder);
376360
}
377361

378-
public Timeout newTimeout(TimerTask task, long delay) {
379-
return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
362+
private void scheduleReadTimeout(NettyResponseFuture<?> nettyResponseFuture) {
363+
nettyResponseFuture.touch();
364+
TimeoutsHolder timeoutsHolder = nettyResponseFuture.getTimeoutsHolder();
365+
timeoutsHolder.startReadTimeout();
380366
}
381367

382368
public void abort(Channel channel, NettyResponseFuture<?> future, Throwable t) {

client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@
2222
public class ReadTimeoutTimerTask extends TimeoutTimerTask {
2323

2424
private final long readTimeout;
25-
private final long requestTimeoutInstant;
2625

2726
public ReadTimeoutTimerTask(//
2827
NettyResponseFuture<?> nettyResponseFuture,//
2928
NettyRequestSender requestSender,//
3029
TimeoutsHolder timeoutsHolder,//
31-
long requestTimeout,//
3230
long readTimeout) {
3331
super(nettyResponseFuture, requestSender, timeoutsHolder);
3432
this.readTimeout = readTimeout;
35-
requestTimeoutInstant = requestTimeout >= 0 ? nettyResponseFuture.getStart() + requestTimeout : Long.MAX_VALUE;
3633
}
3734

3835
public void run(Timeout timeout) throws Exception {
@@ -52,20 +49,15 @@ public void run(Timeout timeout) throws Exception {
5249

5350
if (durationBeforeCurrentReadTimeout <= 0L) {
5451
// idleConnectTimeout reached
55-
String message = "Read timeout to " + remoteAddress + " after " + readTimeout + " ms";
52+
String message = "Read timeout to " + timeoutsHolder.remoteAddress() + " after " + readTimeout + " ms";
5653
long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch();
5754
expire(message, durationSinceLastTouch);
5855
// cancel request timeout sibling
5956
timeoutsHolder.cancel();
6057

61-
} else if (currentReadTimeoutInstant < requestTimeoutInstant) {
62-
// reschedule
63-
done.set(false);
64-
timeoutsHolder.readTimeout = requestSender.newTimeout(this, durationBeforeCurrentReadTimeout);
65-
6658
} else {
67-
// otherwise, no need to reschedule: requestTimeout will happen sooner
68-
timeoutsHolder.readTimeout = null;
59+
done.set(false);
60+
timeoutsHolder.startReadTimeout(this);
6961
}
7062
}
7163
}

client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void run(Timeout timeout) throws Exception {
4343
if (nettyResponseFuture.isDone())
4444
return;
4545

46-
String message = "Request timeout to " + remoteAddress + " after " + requestTimeout + "ms";
46+
String message = "Request timeout to " + timeoutsHolder.remoteAddress() + " after " + requestTimeout + "ms";
4747
long age = millisTime() - nettyResponseFuture.getStart();
4848
expire(message, age);
4949
}

client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
*/
1414
package org.asynchttpclient.netty.timeout;
1515

16-
import io.netty.channel.Channel;
1716
import io.netty.util.TimerTask;
1817

19-
import java.net.SocketAddress;
2018
import java.util.concurrent.TimeoutException;
2119
import java.util.concurrent.atomic.AtomicBoolean;
2220

@@ -33,16 +31,11 @@ public abstract class TimeoutTimerTask implements TimerTask {
3331
protected volatile NettyResponseFuture<?> nettyResponseFuture;
3432
protected final NettyRequestSender requestSender;
3533
protected final TimeoutsHolder timeoutsHolder;
36-
protected final String remoteAddress;
3734

3835
public TimeoutTimerTask(NettyResponseFuture<?> nettyResponseFuture, NettyRequestSender requestSender, TimeoutsHolder timeoutsHolder) {
3936
this.nettyResponseFuture = nettyResponseFuture;
4037
this.requestSender = requestSender;
4138
this.timeoutsHolder = timeoutsHolder;
42-
// saving remote address as the channel might be removed from the future when an exception occurs
43-
Channel channel = nettyResponseFuture.channel();
44-
SocketAddress sa = channel == null ? null : channel.remoteAddress();
45-
remoteAddress = sa == null ? "not-connected" : sa.toString();
4639
}
4740

4841
protected void expire(String message, long time) {
@@ -55,7 +48,8 @@ protected void expire(String message, long time) {
5548
* Holding a reference to the future might mean holding a reference to the channel, and heavy objects such as SslEngines
5649
*/
5750
public void clean() {
58-
if (done.compareAndSet(false, true))
51+
if (done.compareAndSet(false, true)) {
5952
nettyResponseFuture = null;
53+
}
6054
}
6155
}

client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,106 @@
1313
*/
1414
package org.asynchttpclient.netty.timeout;
1515

16+
import static org.asynchttpclient.util.DateUtils.millisTime;
17+
import io.netty.channel.Channel;
1618
import io.netty.util.Timeout;
19+
import io.netty.util.Timer;
20+
import io.netty.util.TimerTask;
1721

22+
import java.net.SocketAddress;
23+
import java.util.concurrent.TimeUnit;
1824
import java.util.concurrent.atomic.AtomicBoolean;
1925

26+
import org.asynchttpclient.AsyncHttpClientConfig;
27+
import org.asynchttpclient.netty.NettyResponseFuture;
28+
import org.asynchttpclient.netty.request.NettyRequestSender;
29+
2030
public class TimeoutsHolder {
2131

2232
private final AtomicBoolean cancelled = new AtomicBoolean();
23-
public volatile Timeout requestTimeout;
33+
34+
private final Timer nettyTimer;
35+
private final NettyRequestSender requestSender;
36+
private final long requestTimeoutMillisTime;
37+
private final int readTimeoutValue;
38+
39+
private volatile NettyResponseFuture<?> nettyResponseFuture;
40+
public final Timeout requestTimeout;
2441
public volatile Timeout readTimeout;
42+
private volatile String remoteAddress = "not-connected";
43+
44+
public TimeoutsHolder(Timer nettyTimer, NettyResponseFuture<?> nettyResponseFuture, NettyRequestSender requestSender, AsyncHttpClientConfig config) {
45+
this.nettyTimer = nettyTimer;
46+
this.nettyResponseFuture = nettyResponseFuture;
47+
this.requestSender = requestSender;
48+
this.readTimeoutValue = config.getReadTimeout();
49+
50+
int requestTimeoutInMs = nettyResponseFuture.getTargetRequest().getRequestTimeout();
51+
if (requestTimeoutInMs == 0) {
52+
requestTimeoutInMs = config.getRequestTimeout();
53+
}
54+
55+
if (requestTimeoutInMs != -1) {
56+
requestTimeoutMillisTime = millisTime() + requestTimeoutInMs;
57+
requestTimeout = newTimeout(new RequestTimeoutTimerTask(nettyResponseFuture, requestSender, this, requestTimeoutInMs), requestTimeoutInMs);
58+
} else {
59+
requestTimeoutMillisTime = -1L;
60+
requestTimeout = null;
61+
}
62+
}
63+
64+
private void initRemoteAddress() {
65+
Channel channel = nettyResponseFuture.channel();
66+
if (channel != null) {
67+
SocketAddress sa = channel.remoteAddress();
68+
if (sa != null) {
69+
remoteAddress = sa.toString();
70+
}
71+
}
72+
}
73+
74+
public void startReadTimeout() {
75+
// we should be connected now
76+
initRemoteAddress();
77+
if (readTimeoutValue != -1) {
78+
startReadTimeout(null);
79+
}
80+
}
81+
82+
void startReadTimeout(ReadTimeoutTimerTask task) {
83+
if (requestTimeout == null || (!requestTimeout.isExpired() && readTimeoutValue > (requestTimeoutMillisTime - millisTime()))) {
84+
// only schedule a new readTimeout if the requestTimeout doesn't happen first
85+
if (task == null) {
86+
// first call triggered from outside (else is read timeout is re-scheduling itself)
87+
task = new ReadTimeoutTimerTask(nettyResponseFuture, requestSender, this, readTimeoutValue);
88+
}
89+
Timeout readTimeout = newTimeout(task, readTimeoutValue);
90+
this.readTimeout = readTimeout;
91+
92+
} else if (task != null) {
93+
// read timeout couldn't re-scheduling itself, clean up
94+
task.clean();
95+
}
96+
}
2597

2698
public void cancel() {
2799
if (cancelled.compareAndSet(false, true)) {
28100
if (requestTimeout != null) {
29101
requestTimeout.cancel();
30102
RequestTimeoutTimerTask.class.cast(requestTimeout.task()).clean();
31-
requestTimeout = null;
32103
}
33104
if (readTimeout != null) {
34105
readTimeout.cancel();
35106
ReadTimeoutTimerTask.class.cast(readTimeout.task()).clean();
36-
readTimeout = null;
37107
}
38108
}
39109
}
110+
111+
private Timeout newTimeout(TimerTask task, long delay) {
112+
return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
113+
}
114+
115+
String remoteAddress() {
116+
return remoteAddress;
117+
}
40118
}

client/src/main/java/org/asynchttpclient/util/HttpUtils.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ public static Charset parseCharset(String contentType) {
7878
return null;
7979
}
8080

81-
public static int requestTimeout(AsyncHttpClientConfig config, Request request) {
82-
return request.getRequestTimeout() != 0 ? request.getRequestTimeout() : config.getRequestTimeout();
83-
}
84-
8581
public static boolean followRedirect(AsyncHttpClientConfig config, Request request) {
8682
return request.getFollowRedirect() != null ? request.getFollowRedirect().booleanValue() : config.isFollowRedirect();
8783
}

0 commit comments

Comments
 (0)