diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java index f6a33aeccef..1e80d25551b 100644 --- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java +++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java @@ -18,6 +18,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.concurrent.ImmediateEventExecutor; @@ -25,6 +26,8 @@ import org.junit.Test; import java.net.InetSocketAddress; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import static org.junit.Assert.*; @@ -67,6 +70,46 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { } } + @Test(timeout = 3000) + public void testChannelEventsFiredWhenClosedDirectly() throws Throwable { + run(); + } + + public void testChannelEventsFiredWhenClosedDirectly(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final BlockingQueue events = new LinkedBlockingQueue(); + + Channel sc = null; + Channel cc = null; + try { + sb.childHandler(new ChannelInboundHandlerAdapter()); + sc = sb.bind(0).syncUninterruptibly().channel(); + + cb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + events.add(0); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + events.add(1); + } + }); + // Connect and directly close again. + cc = cb.connect(sc.localAddress()).addListener(ChannelFutureListener.CLOSE). + syncUninterruptibly().channel(); + assertEquals(0, events.take().intValue()); + assertEquals(1, events.take().intValue()); + } finally { + if (cc != null) { + cc.close(); + } + if (sc != null) { + sc.close(); + } + } + } + private static void assertLocalAddress(InetSocketAddress address) { assertTrue(address.getPort() > 0); assertFalse(address.getAddress().isAnyLocalAddress()); diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java index 3cbfe735505..1fd1a5739c6 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java @@ -834,12 +834,16 @@ private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { } active = true; + // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel. + // We still need to ensure we call fireChannelActive() in this case. + boolean active = isActive(); + // trySuccess() will return false if a user cancelled the connection attempt. boolean promiseSet = promise.trySuccess(); // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, // because what happened is what happened. - if (!wasActive && isActive()) { + if (!wasActive && active) { pipeline().fireChannelActive(); } diff --git a/transport/src/main/java/io/netty/channel/nio/AbstractNioChannel.java b/transport/src/main/java/io/netty/channel/nio/AbstractNioChannel.java index 89c5d0fe801..bf955fc7934 100644 --- a/transport/src/main/java/io/netty/channel/nio/AbstractNioChannel.java +++ b/transport/src/main/java/io/netty/channel/nio/AbstractNioChannel.java @@ -296,12 +296,16 @@ private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { return; } + // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel. + // We still need to ensure we call fireChannelActive() in this case. + boolean active = isActive(); + // trySuccess() will return false if a user cancelled the connection attempt. boolean promiseSet = promise.trySuccess(); // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, // because what happened is what happened. - if (!wasActive && isActive()) { + if (!wasActive && active) { pipeline().fireChannelActive(); } diff --git a/transport/src/main/java/io/netty/channel/oio/AbstractOioChannel.java b/transport/src/main/java/io/netty/channel/oio/AbstractOioChannel.java index a42cdddc933..7aa312d8e90 100644 --- a/transport/src/main/java/io/netty/channel/oio/AbstractOioChannel.java +++ b/transport/src/main/java/io/netty/channel/oio/AbstractOioChannel.java @@ -68,8 +68,13 @@ public void connect( try { boolean wasActive = isActive(); doConnect(remoteAddress, localAddress); + + // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel. + // We still need to ensure we call fireChannelActive() in this case. + boolean active = isActive(); + safeSetSuccess(promise); - if (!wasActive && isActive()) { + if (!wasActive && active) { pipeline().fireChannelActive(); } } catch (Throwable t) {