-
-
Notifications
You must be signed in to change notification settings - Fork 16.3k
NioEventLoop takes 1 second to shutdown if there are open connections #4241
Description
Given the following:
package io.netty.handler.codec.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;
import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class SlowShutdownTest {
private static final byte[] CONTENT = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
public static void main(String... args) throws InterruptedException, IOException {
NioEventLoopGroup loop = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(loop)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new HttpServerCodec())
.addLast(new SimpleChannelInboundHandler<HttpRequest>() {
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest req) {
boolean keepAlive = HttpUtil.isKeepAlive(req);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
}
});
}
});
Channel channel = b.bind(8080).sync().channel();
URLConnection urlConnection = new URL("http://localhost:8080").openConnection();
urlConnection.connect();
urlConnection.getInputStream().close();
channel.close().sync();
loop.shutdownGracefully(0, 1, TimeUnit.MINUTES);
long start = System.currentTimeMillis();
loop.awaitTermination(1, TimeUnit.MINUTES);
long stop = System.currentTimeMillis();
System.out.println("Termination took millis: " + (stop - start));
}
}Termination will take a second. This is problematic as it slows down testing.
The use of keep alive is significant.
I've tracked this down to being due to the event loop being “blocked” in a select here: https://github.com/netty/netty/blob/4.1/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java#L621
It looks to me like the problem is that selector.select() is called even though the loop isShuttingDown(), and the 1 second delay corresponds to the timeoutMillis value given to this method.
I was able to “fix” this by adding:
if (isShuttingDown()) {
break;
}Just before: https://github.com/netty/netty/blob/4.1/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java#L612
I'm not expert in this code and acknowledge that's probably not the best fix.
The fundamental problem seems to be that selector.select() is performed after the loop has started shutting down.