|
6 | 6 | import java.net.InetAddress; |
7 | 7 | import java.net.InetSocketAddress; |
8 | 8 | import java.net.SocketAddress; |
| 9 | +import java.net.SocketTimeoutException; |
9 | 10 | import java.security.Security; |
| 11 | +import java.util.concurrent.TimeUnit; |
10 | 12 |
|
11 | 13 | import javax.net.ssl.SSLEngine; |
12 | 14 | import javax.net.ssl.SSLParameters; |
|
23 | 25 | import io.netty.channel.Channel; |
24 | 26 | import io.netty.channel.ChannelConfig; |
25 | 27 | import io.netty.channel.ChannelFactory; |
| 28 | +import io.netty.channel.ChannelHandlerContext; |
26 | 29 | import io.netty.channel.ChannelInitializer; |
27 | 30 | import io.netty.channel.EventLoopGroup; |
28 | 31 | import io.netty.channel.epoll.EpollDomainSocketChannel; |
|
38 | 41 | import io.netty.handler.codec.http.HttpClientCodec; |
39 | 42 | import io.netty.handler.logging.LoggingHandler; |
40 | 43 | import io.netty.handler.ssl.SslHandler; |
| 44 | +import io.netty.handler.timeout.IdleState; |
| 45 | +import io.netty.handler.timeout.IdleStateEvent; |
| 46 | +import io.netty.handler.timeout.IdleStateHandler; |
41 | 47 | import io.netty.util.concurrent.DefaultThreadFactory; |
42 | 48 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
43 | 49 |
|
@@ -84,6 +90,8 @@ public DuplexChannel getChannel() { |
84 | 90 |
|
85 | 91 | private Integer connectTimeout = null; |
86 | 92 |
|
| 93 | + private Integer readTimeout = null; |
| 94 | + |
87 | 95 | @Override |
88 | 96 | public void init(DockerClientConfig dockerClientConfig) { |
89 | 97 | super.init(dockerClientConfig); |
@@ -273,16 +281,54 @@ public NettyDockerCmdExecFactory withConnectTimeout(Integer connectTimeout) { |
273 | 281 | return this; |
274 | 282 | } |
275 | 283 |
|
| 284 | + /** |
| 285 | + * Configure read timeout in milliseconds |
| 286 | + */ |
| 287 | + public NettyDockerCmdExecFactory withReadTimeout(Integer readTimeout) { |
| 288 | + this.readTimeout = readTimeout; |
| 289 | + return this; |
| 290 | + } |
| 291 | + |
276 | 292 | private <T extends Channel> T configure(T channel) { |
277 | 293 | ChannelConfig channelConfig = channel.config(); |
278 | 294 |
|
279 | 295 | if (connectTimeout != null) { |
280 | 296 | channelConfig.setConnectTimeoutMillis(connectTimeout); |
281 | 297 | } |
| 298 | + if (readTimeout != null) { |
| 299 | + channel.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler()); |
| 300 | + } |
282 | 301 |
|
283 | 302 | return channel; |
284 | 303 | } |
285 | 304 |
|
| 305 | + private final class ReadTimeoutHandler extends IdleStateHandler { |
| 306 | + private boolean alreadyTimedOut; |
| 307 | + |
| 308 | + ReadTimeoutHandler() { |
| 309 | + super(readTimeout, 0, 0, TimeUnit.MILLISECONDS); |
| 310 | + } |
| 311 | + |
| 312 | + /** |
| 313 | + * Called when a read timeout was detected. |
| 314 | + */ |
| 315 | + @Override |
| 316 | + protected synchronized void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { |
| 317 | + assert evt.state() == IdleState.READER_IDLE; |
| 318 | + final Channel channel = ctx.channel(); |
| 319 | + if (channel == null || !channel.isActive() || alreadyTimedOut) { |
| 320 | + return; |
| 321 | + } |
| 322 | + final Object dockerAPIEndpoint = dockerClientConfig.getDockerHost(); |
| 323 | + final String msg = "Read timed out: No data received within " + readTimeout |
| 324 | + + "ms. Perhaps the docker API (" + dockerAPIEndpoint |
| 325 | + + ") is not responding normally, or perhaps you need to increase the readTimeout value."; |
| 326 | + final Exception ex = new SocketTimeoutException(msg); |
| 327 | + ctx.fireExceptionCaught(ex); |
| 328 | + alreadyTimedOut = true; |
| 329 | + } |
| 330 | + } |
| 331 | + |
286 | 332 | protected WebTarget getBaseResource() { |
287 | 333 | checkNotNull(baseResource, "Factory not initialized, baseResource not set. You probably forgot to call init()!"); |
288 | 334 | return baseResource; |
|
0 commit comments