|
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; |
|
153 | 155 | import io.netty.channel.Channel; |
154 | 156 | import io.netty.channel.ChannelConfig; |
155 | 157 | import io.netty.channel.ChannelFactory; |
| 158 | +import io.netty.channel.ChannelHandlerContext; |
156 | 159 | import io.netty.channel.ChannelInitializer; |
157 | 160 | import io.netty.channel.EventLoopGroup; |
158 | 161 | import io.netty.channel.epoll.EpollDomainSocketChannel; |
|
168 | 171 | import io.netty.handler.codec.http.HttpClientCodec; |
169 | 172 | import io.netty.handler.logging.LoggingHandler; |
170 | 173 | import io.netty.handler.ssl.SslHandler; |
| 174 | +import io.netty.handler.timeout.IdleState; |
| 175 | +import io.netty.handler.timeout.IdleStateEvent; |
| 176 | +import io.netty.handler.timeout.IdleStateHandler; |
171 | 177 | import io.netty.util.concurrent.DefaultThreadFactory; |
172 | 178 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
173 | 179 |
|
@@ -216,6 +222,8 @@ public DuplexChannel getChannel() { |
216 | 222 |
|
217 | 223 | private Integer connectTimeout = null; |
218 | 224 |
|
| 225 | + private Integer readTimeout = null; |
| 226 | + |
219 | 227 | @Override |
220 | 228 | public void init(DockerClientConfig dockerClientConfig) { |
221 | 229 | checkNotNull(dockerClientConfig, "config was not specified"); |
@@ -744,16 +752,54 @@ public NettyDockerCmdExecFactory withConnectTimeout(Integer connectTimeout) { |
744 | 752 | return this; |
745 | 753 | } |
746 | 754 |
|
| 755 | + /** |
| 756 | + * Configure read timeout in milliseconds |
| 757 | + */ |
| 758 | + public NettyDockerCmdExecFactory withReadTimeout(Integer readTimeout) { |
| 759 | + this.readTimeout = readTimeout; |
| 760 | + return this; |
| 761 | + } |
| 762 | + |
747 | 763 | private <T extends Channel> T configure(T channel) { |
748 | 764 | ChannelConfig channelConfig = channel.config(); |
749 | 765 |
|
750 | 766 | if (connectTimeout != null) { |
751 | 767 | channelConfig.setConnectTimeoutMillis(connectTimeout); |
752 | 768 | } |
| 769 | + if (readTimeout != null) { |
| 770 | + channel.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler()); |
| 771 | + } |
753 | 772 |
|
754 | 773 | return channel; |
755 | 774 | } |
756 | 775 |
|
| 776 | + private final class ReadTimeoutHandler extends IdleStateHandler { |
| 777 | + private boolean alreadyTimedOut; |
| 778 | + |
| 779 | + ReadTimeoutHandler() { |
| 780 | + super(readTimeout, 0, 0, TimeUnit.MILLISECONDS); |
| 781 | + } |
| 782 | + |
| 783 | + /** |
| 784 | + * Called when a read timeout was detected. |
| 785 | + */ |
| 786 | + @Override |
| 787 | + protected synchronized void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { |
| 788 | + assert evt.state() == IdleState.READER_IDLE; |
| 789 | + final Channel channel = ctx.channel(); |
| 790 | + if (channel == null || !channel.isActive() || alreadyTimedOut) { |
| 791 | + return; |
| 792 | + } |
| 793 | + final Object dockerAPIEndpoint = dockerClientConfig.getDockerHost(); |
| 794 | + final String msg = "Read timed out: No data received within " + readTimeout |
| 795 | + + "ms. Perhaps the docker API (" + dockerAPIEndpoint |
| 796 | + + ") is not responding normally, or perhaps you need to increase the readTimeout value."; |
| 797 | + final Exception ex = new SocketTimeoutException(msg); |
| 798 | + ctx.fireExceptionCaught(ex); |
| 799 | + alreadyTimedOut = true; |
| 800 | + } |
| 801 | + } |
| 802 | + |
757 | 803 | protected WebTarget getBaseResource() { |
758 | 804 | checkNotNull(baseResource, "Factory not initialized, baseResource not set. You probably forgot to call init()!"); |
759 | 805 | return baseResource; |
|
0 commit comments