Skip to content

NioEventLoop takes 1 second to shutdown if there are open connections #4241

@ldaley

Description

@ldaley

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions