1791 lines
62 KiB
Java
1791 lines
62 KiB
Java
/*
|
|
* Copyright 2012 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.channel;
|
|
|
|
|
|
import io.netty.bootstrap.Bootstrap;
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
import io.netty.channel.ChannelHandler.Sharable;
|
|
import io.netty.channel.ChannelHandlerMask.Skip;
|
|
import io.netty.channel.embedded.EmbeddedChannel;
|
|
import io.netty.channel.local.LocalAddress;
|
|
import io.netty.channel.local.LocalChannel;
|
|
import io.netty.channel.local.LocalHandler;
|
|
import io.netty.channel.local.LocalServerChannel;
|
|
import io.netty.channel.nio.NioHandler;
|
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
import io.netty.util.AbstractReferenceCounted;
|
|
import io.netty.util.ReferenceCountUtil;
|
|
import io.netty.util.ReferenceCounted;
|
|
import io.netty.util.concurrent.AbstractEventExecutor;
|
|
import io.netty.util.concurrent.EventExecutor;
|
|
import io.netty.util.concurrent.Future;
|
|
import io.netty.util.concurrent.ImmediateEventExecutor;
|
|
import io.netty.util.concurrent.Promise;
|
|
import io.netty.util.concurrent.ScheduledFuture;
|
|
import org.junit.After;
|
|
import org.junit.AfterClass;
|
|
import org.junit.Test;
|
|
|
|
import java.net.SocketAddress;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Queue;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertNull;
|
|
import static org.junit.Assert.assertSame;
|
|
import static org.junit.Assert.assertTrue;
|
|
import static org.junit.Assert.fail;
|
|
|
|
public class DefaultChannelPipelineTest {
|
|
|
|
private static final EventLoopGroup group = new MultithreadEventLoopGroup(1, LocalHandler.newFactory());
|
|
|
|
private Channel self;
|
|
private Channel peer;
|
|
|
|
@AfterClass
|
|
public static void afterClass() throws Exception {
|
|
group.shutdownGracefully().sync();
|
|
}
|
|
|
|
private void setUp(final ChannelHandler... handlers) throws Exception {
|
|
final AtomicReference<Channel> peerRef = new AtomicReference<>();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
sb.group(group).channel(LocalServerChannel.class);
|
|
sb.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
peerRef.set(ctx.channel());
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
ReferenceCountUtil.release(msg);
|
|
}
|
|
});
|
|
|
|
ChannelFuture bindFuture = sb.bind(LocalAddress.ANY).sync();
|
|
|
|
Bootstrap b = new Bootstrap();
|
|
b.group(group).channel(LocalChannel.class);
|
|
b.handler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
protected void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(handlers);
|
|
}
|
|
});
|
|
|
|
self = b.connect(bindFuture.channel().localAddress()).sync().channel();
|
|
peer = peerRef.get();
|
|
|
|
bindFuture.channel().close().sync();
|
|
}
|
|
|
|
@After
|
|
public void tearDown() throws Exception {
|
|
if (peer != null) {
|
|
peer.close();
|
|
peer = null;
|
|
}
|
|
if (self != null) {
|
|
self = null;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testFreeCalled() throws Exception {
|
|
final CountDownLatch free = new CountDownLatch(1);
|
|
|
|
final ReferenceCounted holder = new AbstractReferenceCounted() {
|
|
@Override
|
|
protected void deallocate() {
|
|
free.countDown();
|
|
}
|
|
|
|
@Override
|
|
public ReferenceCounted touch(Object hint) {
|
|
return this;
|
|
}
|
|
};
|
|
|
|
StringInboundHandler handler = new StringInboundHandler();
|
|
setUp(handler);
|
|
|
|
peer.writeAndFlush(holder).sync();
|
|
|
|
assertTrue(free.await(10, TimeUnit.SECONDS));
|
|
assertTrue(handler.called);
|
|
}
|
|
|
|
private static final class StringInboundHandler implements ChannelInboundHandler {
|
|
boolean called;
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
called = true;
|
|
if (!(msg instanceof String)) {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static LocalChannel newLocalChannel() {
|
|
return new LocalChannel(group.next());
|
|
}
|
|
|
|
@Test
|
|
public void testRemoveChannelHandler() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
ChannelHandler handler2 = newHandler();
|
|
ChannelHandler handler3 = newHandler();
|
|
|
|
pipeline.addLast("handler1", handler1);
|
|
pipeline.addLast("handler2", handler2);
|
|
pipeline.addLast("handler3", handler3);
|
|
assertSame(pipeline.get("handler1"), handler1);
|
|
assertSame(pipeline.get("handler2"), handler2);
|
|
assertSame(pipeline.get("handler3"), handler3);
|
|
|
|
pipeline.remove(handler1);
|
|
assertNull(pipeline.get("handler1"));
|
|
pipeline.remove(handler2);
|
|
assertNull(pipeline.get("handler2"));
|
|
pipeline.remove(handler3);
|
|
assertNull(pipeline.get("handler3"));
|
|
}
|
|
|
|
@Test
|
|
public void testRemoveIfExists() {
|
|
DefaultChannelPipeline pipeline = new DefaultChannelPipeline(newLocalChannel());
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
ChannelHandler handler2 = newHandler();
|
|
ChannelHandler handler3 = newHandler();
|
|
|
|
pipeline.addLast("handler1", handler1);
|
|
pipeline.addLast("handler2", handler2);
|
|
pipeline.addLast("handler3", handler3);
|
|
|
|
assertNotNull(pipeline.removeIfExists(handler1));
|
|
assertNull(pipeline.get("handler1"));
|
|
|
|
assertNotNull(pipeline.removeIfExists("handler2"));
|
|
assertNull(pipeline.get("handler2"));
|
|
|
|
assertNotNull(pipeline.removeIfExists(TestHandler.class));
|
|
assertNull(pipeline.get("handler3"));
|
|
}
|
|
|
|
@Test
|
|
public void testRemoveIfExistsDoesNotThrowException() {
|
|
DefaultChannelPipeline pipeline = new DefaultChannelPipeline(newLocalChannel());
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
ChannelHandler handler2 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
|
|
assertNull(pipeline.removeIfExists("handlerXXX"));
|
|
assertNull(pipeline.removeIfExists(handler2));
|
|
|
|
class NonExistingHandler implements ChannelHandler { }
|
|
|
|
assertNull(pipeline.removeIfExists(NonExistingHandler.class));
|
|
assertNotNull(pipeline.get("handler1"));
|
|
}
|
|
|
|
@Test(expected = NoSuchElementException.class)
|
|
public void testRemoveThrowNoSuchElementException() {
|
|
DefaultChannelPipeline pipeline = new DefaultChannelPipeline(newLocalChannel());
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
|
|
pipeline.remove("handlerXXX");
|
|
}
|
|
|
|
@Test
|
|
public void testReplaceChannelHandler() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
pipeline.addLast("handler2", handler1);
|
|
pipeline.addLast("handler3", handler1);
|
|
assertSame(pipeline.get("handler1"), handler1);
|
|
assertSame(pipeline.get("handler2"), handler1);
|
|
assertSame(pipeline.get("handler3"), handler1);
|
|
|
|
ChannelHandler newHandler1 = newHandler();
|
|
pipeline.replace("handler1", "handler1", newHandler1);
|
|
assertSame(pipeline.get("handler1"), newHandler1);
|
|
|
|
ChannelHandler newHandler3 = newHandler();
|
|
pipeline.replace("handler3", "handler3", newHandler3);
|
|
assertSame(pipeline.get("handler3"), newHandler3);
|
|
|
|
ChannelHandler newHandler2 = newHandler();
|
|
pipeline.replace("handler2", "handler2", newHandler2);
|
|
assertSame(pipeline.get("handler2"), newHandler2);
|
|
}
|
|
|
|
@Test
|
|
public void testChannelHandlerContextNavigation() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
final int HANDLER_ARRAY_LEN = 5;
|
|
ChannelHandler[] firstHandlers = newHandlers(HANDLER_ARRAY_LEN);
|
|
ChannelHandler[] lastHandlers = newHandlers(HANDLER_ARRAY_LEN);
|
|
|
|
pipeline.addFirst(firstHandlers);
|
|
pipeline.addLast(lastHandlers);
|
|
|
|
verifyContextNumber(pipeline, HANDLER_ARRAY_LEN * 2);
|
|
}
|
|
|
|
@Test
|
|
public void testFireChannelRegistered() throws Exception {
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.addLast(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) throws Exception {
|
|
ch.pipeline().addLast(new ChannelHandler() {
|
|
@Override
|
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
latch.countDown();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
pipeline.channel().register();
|
|
assertTrue(latch.await(2, TimeUnit.SECONDS));
|
|
}
|
|
|
|
@Test
|
|
public void testPipelineOperation() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
final int handlerNum = 5;
|
|
ChannelHandler[] handlers1 = newHandlers(handlerNum);
|
|
ChannelHandler[] handlers2 = newHandlers(handlerNum);
|
|
|
|
final String prefixX = "x";
|
|
for (int i = 0; i < handlerNum; i++) {
|
|
if (i % 2 == 0) {
|
|
pipeline.addFirst(prefixX + i, handlers1[i]);
|
|
} else {
|
|
pipeline.addLast(prefixX + i, handlers1[i]);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < handlerNum; i++) {
|
|
if (i % 2 != 0) {
|
|
pipeline.addBefore(prefixX + i, String.valueOf(i), handlers2[i]);
|
|
} else {
|
|
pipeline.addAfter(prefixX + i, String.valueOf(i), handlers2[i]);
|
|
}
|
|
}
|
|
|
|
verifyContextNumber(pipeline, handlerNum * 2);
|
|
}
|
|
|
|
@Test
|
|
public void testChannelHandlerContextOrder() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
pipeline.addFirst("1", newHandler());
|
|
pipeline.addLast("10", newHandler());
|
|
|
|
pipeline.addBefore("10", "5", newHandler());
|
|
pipeline.addAfter("1", "3", newHandler());
|
|
pipeline.addBefore("5", "4", newHandler());
|
|
pipeline.addAfter("5", "6", newHandler());
|
|
|
|
pipeline.addBefore("1", "0", newHandler());
|
|
pipeline.addAfter("10", "11", newHandler());
|
|
|
|
DefaultChannelHandlerContext ctx = (DefaultChannelHandlerContext) pipeline.firstContext();
|
|
assertNotNull(ctx);
|
|
while (ctx != null) {
|
|
int i = toInt(ctx.name());
|
|
int j = next(ctx);
|
|
if (j != -1) {
|
|
assertTrue(i < j);
|
|
} else {
|
|
assertNull(ctx.next.next);
|
|
}
|
|
ctx = ctx.next;
|
|
}
|
|
|
|
verifyContextNumber(pipeline, 8);
|
|
}
|
|
|
|
@Test(timeout = 10000)
|
|
public void testLifeCycleAwareness() throws Exception {
|
|
setUp();
|
|
|
|
ChannelPipeline p = self.pipeline();
|
|
|
|
final List<LifeCycleAwareTestHandler> handlers = new ArrayList<>();
|
|
final int COUNT = 20;
|
|
final CountDownLatch addLatch = new CountDownLatch(COUNT);
|
|
for (int i = 0; i < COUNT; i++) {
|
|
final LifeCycleAwareTestHandler handler = new LifeCycleAwareTestHandler("handler-" + i);
|
|
|
|
// Add handler.
|
|
p.addFirst(handler.name, handler);
|
|
self.eventLoop().execute(() -> {
|
|
// Validate handler life-cycle methods called.
|
|
handler.validate(true, false);
|
|
|
|
// Store handler into the list.
|
|
handlers.add(handler);
|
|
|
|
addLatch.countDown();
|
|
});
|
|
}
|
|
addLatch.await();
|
|
|
|
// Change the order of remove operations over all handlers in the pipeline.
|
|
Collections.shuffle(handlers);
|
|
|
|
final CountDownLatch removeLatch = new CountDownLatch(COUNT);
|
|
|
|
for (final LifeCycleAwareTestHandler handler : handlers) {
|
|
assertSame(handler, p.remove(handler.name));
|
|
|
|
self.eventLoop().execute(() -> {
|
|
// Validate handler life-cycle methods called.
|
|
handler.validate(true, true);
|
|
removeLatch.countDown();
|
|
});
|
|
}
|
|
removeLatch.await();
|
|
}
|
|
|
|
@Test(timeout = 100000)
|
|
public void testRemoveAndForwardInbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1, handler2);
|
|
|
|
self.eventLoop().submit(() -> {
|
|
ChannelPipeline p = self.pipeline();
|
|
handler1.inboundBuffer.add(8);
|
|
assertEquals(8, handler1.inboundBuffer.peek());
|
|
assertTrue(handler2.inboundBuffer.isEmpty());
|
|
p.remove(handler1);
|
|
assertEquals(1, handler2.inboundBuffer.size());
|
|
assertEquals(8, handler2.inboundBuffer.peek());
|
|
}).sync();
|
|
}
|
|
|
|
@Test(timeout = 10000)
|
|
public void testRemoveAndForwardOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1, handler2);
|
|
|
|
self.eventLoop().submit(() -> {
|
|
ChannelPipeline p = self.pipeline();
|
|
handler2.outboundBuffer.add(8);
|
|
assertEquals(8, handler2.outboundBuffer.peek());
|
|
assertTrue(handler1.outboundBuffer.isEmpty());
|
|
p.remove(handler2);
|
|
assertEquals(1, handler1.outboundBuffer.size());
|
|
assertEquals(8, handler1.outboundBuffer.peek());
|
|
}).sync();
|
|
}
|
|
|
|
@Test(timeout = 10000)
|
|
public void testReplaceAndForwardOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1);
|
|
|
|
self.eventLoop().submit(() -> {
|
|
ChannelPipeline p = self.pipeline();
|
|
handler1.outboundBuffer.add(8);
|
|
assertEquals(8, handler1.outboundBuffer.peek());
|
|
assertTrue(handler2.outboundBuffer.isEmpty());
|
|
p.replace(handler1, "handler2", handler2);
|
|
assertEquals(8, handler2.outboundBuffer.peek());
|
|
}).sync();
|
|
}
|
|
|
|
@Test(timeout = 10000)
|
|
public void testReplaceAndForwardInboundAndOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1);
|
|
|
|
self.eventLoop().submit(() -> {
|
|
ChannelPipeline p = self.pipeline();
|
|
handler1.inboundBuffer.add(8);
|
|
handler1.outboundBuffer.add(8);
|
|
|
|
assertEquals(8, handler1.inboundBuffer.peek());
|
|
assertEquals(8, handler1.outboundBuffer.peek());
|
|
assertTrue(handler2.inboundBuffer.isEmpty());
|
|
assertTrue(handler2.outboundBuffer.isEmpty());
|
|
|
|
p.replace(handler1, "handler2", handler2);
|
|
assertEquals(8, handler2.outboundBuffer.peek());
|
|
assertEquals(8, handler2.inboundBuffer.peek());
|
|
}).sync();
|
|
}
|
|
|
|
@Test(timeout = 10000)
|
|
public void testRemoveAndForwardInboundOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler3 = new BufferedTestHandler();
|
|
|
|
setUp(handler1, handler2, handler3);
|
|
|
|
self.eventLoop().submit(() -> {
|
|
ChannelPipeline p = self.pipeline();
|
|
handler2.inboundBuffer.add(8);
|
|
handler2.outboundBuffer.add(8);
|
|
|
|
assertEquals(8, handler2.inboundBuffer.peek());
|
|
assertEquals(8, handler2.outboundBuffer.peek());
|
|
|
|
assertEquals(0, handler1.outboundBuffer.size());
|
|
assertEquals(0, handler3.inboundBuffer.size());
|
|
|
|
p.remove(handler2);
|
|
assertEquals(8, handler3.inboundBuffer.peek());
|
|
assertEquals(8, handler1.outboundBuffer.peek());
|
|
}).sync();
|
|
}
|
|
|
|
// Tests for https://github.com/netty/netty/issues/2349
|
|
@Test
|
|
public void testCancelBind() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ChannelFuture future = pipeline.bind(new LocalAddress("test"), promise);
|
|
assertTrue(future.isCancelled());
|
|
}
|
|
|
|
@Test
|
|
public void testCancelConnect() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ChannelFuture future = pipeline.connect(new LocalAddress("test"), promise);
|
|
assertTrue(future.isCancelled());
|
|
}
|
|
|
|
@Test
|
|
public void testCancelDisconnect() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ChannelFuture future = pipeline.disconnect(promise);
|
|
assertTrue(future.isCancelled());
|
|
}
|
|
|
|
@Test
|
|
public void testCancelClose() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ChannelFuture future = pipeline.close(promise);
|
|
assertTrue(future.isCancelled());
|
|
}
|
|
|
|
@Test(expected = IllegalArgumentException.class)
|
|
public void testWrongPromiseChannel() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
ChannelPipeline pipeline2 = newLocalChannel().pipeline();
|
|
pipeline2.channel().register().sync();
|
|
|
|
try {
|
|
ChannelPromise promise2 = pipeline2.channel().newPromise();
|
|
pipeline.close(promise2);
|
|
} finally {
|
|
pipeline.close();
|
|
pipeline2.close();
|
|
}
|
|
}
|
|
|
|
@Test(expected = IllegalArgumentException.class)
|
|
public void testUnexpectedVoidChannelPromise() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
try {
|
|
ChannelPromise promise = new VoidChannelPromise(pipeline.channel(), false);
|
|
pipeline.close(promise);
|
|
} finally {
|
|
pipeline.close();
|
|
}
|
|
}
|
|
|
|
@Test(expected = IllegalArgumentException.class)
|
|
public void testUnexpectedVoidChannelPromiseCloseFuture() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
try {
|
|
ChannelPromise promise = (ChannelPromise) pipeline.channel().closeFuture();
|
|
pipeline.close(promise);
|
|
} finally {
|
|
pipeline.close();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCancelDeregister() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ChannelFuture future = pipeline.deregister(promise);
|
|
assertTrue(future.isCancelled());
|
|
}
|
|
|
|
@Test
|
|
public void testCancelWrite() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ByteBuf buffer = Unpooled.buffer();
|
|
assertEquals(1, buffer.refCnt());
|
|
ChannelFuture future = pipeline.write(buffer, promise);
|
|
assertTrue(future.isCancelled());
|
|
assertEquals(0, buffer.refCnt());
|
|
}
|
|
|
|
@Test
|
|
public void testCancelWriteAndFlush() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
ChannelPromise promise = pipeline.channel().newPromise();
|
|
assertTrue(promise.cancel(false));
|
|
ByteBuf buffer = Unpooled.buffer();
|
|
assertEquals(1, buffer.refCnt());
|
|
ChannelFuture future = pipeline.writeAndFlush(buffer, promise);
|
|
assertTrue(future.isCancelled());
|
|
assertEquals(0, buffer.refCnt());
|
|
}
|
|
|
|
@Test
|
|
public void testFirstContextEmptyPipeline() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
assertNull(pipeline.firstContext());
|
|
}
|
|
|
|
@Test
|
|
public void testLastContextEmptyPipeline() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
assertNull(pipeline.lastContext());
|
|
}
|
|
|
|
@Test
|
|
public void testFirstHandlerEmptyPipeline() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
assertNull(pipeline.first());
|
|
}
|
|
|
|
@Test
|
|
public void testLastHandlerEmptyPipeline() throws Exception {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
assertNull(pipeline.last());
|
|
}
|
|
|
|
@Test(timeout = 5000)
|
|
public void testChannelInitializerException() throws Exception {
|
|
final IllegalStateException exception = new IllegalStateException();
|
|
final AtomicReference<Throwable> error = new AtomicReference<>();
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
EmbeddedChannel channel = new EmbeddedChannel(false, false, new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) throws Exception {
|
|
throw exception;
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
super.exceptionCaught(ctx, cause);
|
|
error.set(cause);
|
|
latch.countDown();
|
|
}
|
|
});
|
|
latch.await();
|
|
assertFalse(channel.isActive());
|
|
assertSame(exception, error.get());
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddHandlerBeforeRegisteredThenRemove() {
|
|
final EventLoop loop = group.next();
|
|
|
|
CheckEventExecutorHandler handler = new CheckEventExecutorHandler(loop);
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.addFirst(handler);
|
|
handler.addedPromise.syncUninterruptibly();
|
|
pipeline.channel().register();
|
|
pipeline.remove(handler);
|
|
handler.removedPromise.syncUninterruptibly();
|
|
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddHandlerBeforeRegisteredThenReplace() throws Exception {
|
|
final EventLoop loop = group.next();
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
CheckEventExecutorHandler handler = new CheckEventExecutorHandler(loop);
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.addFirst(handler);
|
|
handler.addedPromise.syncUninterruptibly();
|
|
pipeline.channel().register();
|
|
pipeline.replace(handler, null, new ChannelHandlerAdapter() {
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
latch.countDown();
|
|
}
|
|
});
|
|
handler.removedPromise.syncUninterruptibly();
|
|
latch.await();
|
|
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
|
|
@Test(timeout = 2000)
|
|
public void testAddRemoveHandlerCalled() throws Throwable {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
CallbackCheckHandler handler = new CallbackCheckHandler();
|
|
|
|
pipeline.addFirst(handler);
|
|
pipeline.remove(handler);
|
|
|
|
assertTrue(handler.addedHandler.get());
|
|
assertTrue(handler.removedHandler.get());
|
|
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
Throwable cause = handler.error.get();
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
|
|
if (cause != null) {
|
|
throw cause;
|
|
}
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddReplaceHandlerCalled() throws Throwable {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
CallbackCheckHandler handler = new CallbackCheckHandler();
|
|
CallbackCheckHandler handler2 = new CallbackCheckHandler();
|
|
|
|
pipeline.addFirst(handler);
|
|
pipeline.replace(handler, null, handler2);
|
|
|
|
assertTrue(handler.addedHandler.get());
|
|
assertTrue(handler.removedHandler.get());
|
|
assertTrue(handler2.addedHandler.get());
|
|
assertNull(handler2.removedHandler.getNow());
|
|
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
Throwable cause = handler.error.get();
|
|
if (cause != null) {
|
|
throw cause;
|
|
}
|
|
|
|
Throwable cause2 = handler2.error.get();
|
|
if (cause2 != null) {
|
|
throw cause2;
|
|
}
|
|
|
|
assertNull(handler2.removedHandler.getNow());
|
|
pipeline.remove(handler2);
|
|
assertTrue(handler2.removedHandler.get());
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddBefore() throws Throwable {
|
|
EventLoopGroup defaultGroup = new MultithreadEventLoopGroup(2, LocalHandler.newFactory());
|
|
try {
|
|
EventLoop eventLoop1 = defaultGroup.next();
|
|
EventLoop eventLoop2 = defaultGroup.next();
|
|
|
|
ChannelPipeline pipeline1 = new LocalChannel(eventLoop1).pipeline();
|
|
ChannelPipeline pipeline2 = new LocalChannel(eventLoop2).pipeline();
|
|
|
|
pipeline1.channel().register().syncUninterruptibly();
|
|
pipeline2.channel().register().syncUninterruptibly();
|
|
|
|
CountDownLatch latch = new CountDownLatch(2 * 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
eventLoop1.execute(new TestTask(pipeline2, latch));
|
|
eventLoop2.execute(new TestTask(pipeline1, latch));
|
|
}
|
|
latch.await();
|
|
pipeline1.channel().close().syncUninterruptibly();
|
|
pipeline2.channel().close().syncUninterruptibly();
|
|
} finally {
|
|
defaultGroup.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddInListenerNio() throws Throwable {
|
|
EventLoopGroup nioEventLoopGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
|
|
try {
|
|
testAddInListener(new NioSocketChannel(nioEventLoopGroup.next()));
|
|
} finally {
|
|
nioEventLoopGroup.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testAddInListenerLocal() throws Throwable {
|
|
testAddInListener(newLocalChannel());
|
|
}
|
|
|
|
private static void testAddInListener(Channel channel) throws Throwable {
|
|
ChannelPipeline pipeline1 = channel.pipeline();
|
|
try {
|
|
final Object event = new Object();
|
|
final Promise<Object> promise = ImmediateEventExecutor.INSTANCE.newPromise();
|
|
pipeline1.channel().register().addListener((ChannelFutureListener) future -> {
|
|
ChannelPipeline pipeline = future.channel().pipeline();
|
|
final AtomicBoolean handlerAddedCalled = new AtomicBoolean();
|
|
pipeline.addLast(new ChannelHandler() {
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
handlerAddedCalled.set(true);
|
|
}
|
|
|
|
@Override
|
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
promise.setSuccess(event);
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
promise.setFailure(cause);
|
|
}
|
|
});
|
|
if (!handlerAddedCalled.get()) {
|
|
promise.setFailure(new AssertionError("handlerAdded(...) should have been called"));
|
|
return;
|
|
}
|
|
// This event must be captured by the added handler.
|
|
pipeline.fireUserEventTriggered(event);
|
|
});
|
|
assertSame(event, promise.syncUninterruptibly().getNow());
|
|
} finally {
|
|
pipeline1.channel().close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testNullName() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
pipeline.addLast(newHandler());
|
|
pipeline.addLast(null, newHandler());
|
|
pipeline.addFirst(newHandler());
|
|
pipeline.addFirst(null, newHandler());
|
|
|
|
pipeline.addLast("test", newHandler());
|
|
pipeline.addAfter("test", null, newHandler());
|
|
|
|
pipeline.addBefore("test", null, newHandler());
|
|
}
|
|
|
|
@Test(timeout = 3000)
|
|
public void testVoidPromiseNotify() throws Throwable {
|
|
EventLoopGroup defaultGroup = new MultithreadEventLoopGroup(1, LocalHandler.newFactory());
|
|
EventLoop eventLoop1 = defaultGroup.next();
|
|
ChannelPipeline pipeline1 = new LocalChannel(eventLoop1).pipeline();
|
|
|
|
final Promise<Throwable> promise = eventLoop1.newPromise();
|
|
final Exception exception = new IllegalArgumentException();
|
|
try {
|
|
pipeline1.channel().register().syncUninterruptibly();
|
|
pipeline1.addLast(new ChannelHandler() {
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
throw exception;
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
promise.setSuccess(cause);
|
|
}
|
|
});
|
|
pipeline1.write("test", pipeline1.voidPromise());
|
|
assertSame(exception, promise.syncUninterruptibly().getNow());
|
|
} finally {
|
|
pipeline1.channel().close().syncUninterruptibly();
|
|
defaultGroup.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
// Test for https://github.com/netty/netty/issues/8676.
|
|
@Test
|
|
public void testHandlerRemovedOnlyCalledWhenHandlerAddedCalled() throws Exception {
|
|
EventLoopGroup group = new MultithreadEventLoopGroup(1, LocalHandler.newFactory());
|
|
try {
|
|
final AtomicReference<Error> errorRef = new AtomicReference<>();
|
|
|
|
// As this only happens via a race we will verify 500 times. This was good enough to have it failed most of
|
|
// the time.
|
|
for (int i = 0; i < 500; i++) {
|
|
|
|
ChannelPipeline pipeline = new LocalChannel(group.next()).pipeline();
|
|
pipeline.channel().register().sync();
|
|
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
pipeline.addLast(new ChannelHandler() {
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
// Block just for a bit so we have a chance to trigger the race mentioned in the issue.
|
|
latch.await(50, TimeUnit.MILLISECONDS);
|
|
}
|
|
});
|
|
|
|
// Close the pipeline which will call destroy0(). This will remove each handler in the pipeline and
|
|
// should call handlerRemoved(...) if and only if handlerAdded(...) was called for the handler before.
|
|
pipeline.close();
|
|
|
|
pipeline.addLast(new ChannelHandler() {
|
|
private boolean handerAddedCalled;
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
handerAddedCalled = true;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
if (!handerAddedCalled) {
|
|
errorRef.set(new AssertionError(
|
|
"handlerRemoved(...) called without handlerAdded(...) before"));
|
|
}
|
|
}
|
|
});
|
|
|
|
latch.countDown();
|
|
|
|
pipeline.channel().closeFuture().syncUninterruptibly();
|
|
|
|
// Schedule something on the EventLoop to ensure all other scheduled tasks had a chance to complete.
|
|
pipeline.channel().eventLoop().submit(() -> {
|
|
// NOOP
|
|
}).syncUninterruptibly();
|
|
Error error = errorRef.get();
|
|
if (error != null) {
|
|
throw error;
|
|
}
|
|
}
|
|
} finally {
|
|
group.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSkipHandlerMethodsIfAnnotated() {
|
|
EmbeddedChannel channel = new EmbeddedChannel(true);
|
|
ChannelPipeline pipeline = channel.pipeline();
|
|
|
|
final class SkipHandler implements ChannelInboundHandler, ChannelOutboundHandler {
|
|
private int state = 2;
|
|
private Error errorRef;
|
|
|
|
private void fail() {
|
|
errorRef = new AssertionError("Method should never been called");
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
|
|
fail();
|
|
ctx.bind(localAddress, promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
|
SocketAddress localAddress, ChannelPromise promise) {
|
|
fail();
|
|
ctx.connect(remoteAddress, localAddress, promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
fail();
|
|
ctx.disconnect(promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
fail();
|
|
ctx.close(promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void register(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
fail();
|
|
ctx.register(promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
fail();
|
|
ctx.deregister(promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void read(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.read();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
fail();
|
|
ctx.write(msg, promise);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void flush(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.flush();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelRegistered(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelRegistered();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelUnregistered(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelUnregistered();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelActive();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelInactive();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
fail();
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelReadComplete(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelReadComplete();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
|
fail();
|
|
ctx.fireUserEventTriggered(evt);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.fireChannelWritabilityChanged();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
fail();
|
|
ctx.fireExceptionCaught(cause);
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
state--;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
state--;
|
|
}
|
|
|
|
void assertSkipped() {
|
|
assertEquals(0, state);
|
|
Error error = errorRef;
|
|
if (error != null) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
final class OutboundCalledHandler implements ChannelOutboundHandler {
|
|
private static final int MASK_BIND = 1;
|
|
private static final int MASK_CONNECT = 1 << 1;
|
|
private static final int MASK_DISCONNECT = 1 << 2;
|
|
private static final int MASK_CLOSE = 1 << 3;
|
|
private static final int MASK_REGISTER = 1 << 4;
|
|
private static final int MASK_DEREGISTER = 1 << 5;
|
|
private static final int MASK_READ = 1 << 6;
|
|
private static final int MASK_WRITE = 1 << 7;
|
|
private static final int MASK_FLUSH = 1 << 8;
|
|
private static final int MASK_ADDED = 1 << 9;
|
|
private static final int MASK_REMOVED = 1 << 10;
|
|
|
|
private int executionMask;
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_ADDED;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_REMOVED;
|
|
}
|
|
|
|
@Override
|
|
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
|
|
executionMask |= MASK_BIND;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
|
SocketAddress localAddress, ChannelPromise promise) {
|
|
executionMask |= MASK_CONNECT;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
executionMask |= MASK_DISCONNECT;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
executionMask |= MASK_CLOSE;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void register(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
executionMask |= MASK_REGISTER;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
executionMask |= MASK_DEREGISTER;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void read(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_READ;
|
|
}
|
|
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
executionMask |= MASK_WRITE;
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void flush(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_FLUSH;
|
|
}
|
|
|
|
void assertCalled() {
|
|
assertCalled("handlerAdded", MASK_ADDED);
|
|
assertCalled("handlerRemoved", MASK_REMOVED);
|
|
assertCalled("bind", MASK_BIND);
|
|
assertCalled("connect", MASK_CONNECT);
|
|
assertCalled("disconnect", MASK_DISCONNECT);
|
|
assertCalled("close", MASK_CLOSE);
|
|
assertCalled("register", MASK_REGISTER);
|
|
assertCalled("deregister", MASK_DEREGISTER);
|
|
assertCalled("read", MASK_READ);
|
|
assertCalled("write", MASK_WRITE);
|
|
assertCalled("flush", MASK_FLUSH);
|
|
}
|
|
|
|
private void assertCalled(String methodName, int mask) {
|
|
assertTrue(methodName + " was not called", (executionMask & mask) != 0);
|
|
}
|
|
}
|
|
|
|
final class InboundCalledHandler implements ChannelInboundHandler {
|
|
|
|
private static final int MASK_CHANNEL_REGISTER = 1;
|
|
private static final int MASK_CHANNEL_UNREGISTER = 1 << 1;
|
|
private static final int MASK_CHANNEL_ACTIVE = 1 << 2;
|
|
private static final int MASK_CHANNEL_INACTIVE = 1 << 3;
|
|
private static final int MASK_CHANNEL_READ = 1 << 4;
|
|
private static final int MASK_CHANNEL_READ_COMPLETE = 1 << 5;
|
|
private static final int MASK_USER_EVENT_TRIGGERED = 1 << 6;
|
|
private static final int MASK_CHANNEL_WRITABILITY_CHANGED = 1 << 7;
|
|
private static final int MASK_EXCEPTION_CAUGHT = 1 << 8;
|
|
private static final int MASK_ADDED = 1 << 9;
|
|
private static final int MASK_REMOVED = 1 << 10;
|
|
|
|
private int executionMask;
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_ADDED;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_REMOVED;
|
|
}
|
|
|
|
@Override
|
|
public void channelRegistered(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_REGISTER;
|
|
}
|
|
|
|
@Override
|
|
public void channelUnregistered(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_UNREGISTER;
|
|
}
|
|
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_ACTIVE;
|
|
}
|
|
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_INACTIVE;
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
executionMask |= MASK_CHANNEL_READ;
|
|
}
|
|
|
|
@Override
|
|
public void channelReadComplete(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_READ_COMPLETE;
|
|
}
|
|
|
|
@Override
|
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
|
executionMask |= MASK_USER_EVENT_TRIGGERED;
|
|
}
|
|
|
|
@Override
|
|
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CHANNEL_WRITABILITY_CHANGED;
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
executionMask |= MASK_EXCEPTION_CAUGHT;
|
|
}
|
|
|
|
void assertCalled() {
|
|
assertCalled("handlerAdded", MASK_ADDED);
|
|
assertCalled("handlerRemoved", MASK_REMOVED);
|
|
assertCalled("channelRegistered", MASK_CHANNEL_REGISTER);
|
|
assertCalled("channelUnregistered", MASK_CHANNEL_UNREGISTER);
|
|
assertCalled("channelActive", MASK_CHANNEL_ACTIVE);
|
|
assertCalled("channelInactive", MASK_CHANNEL_INACTIVE);
|
|
assertCalled("channelRead", MASK_CHANNEL_READ);
|
|
assertCalled("channelReadComplete", MASK_CHANNEL_READ_COMPLETE);
|
|
assertCalled("userEventTriggered", MASK_USER_EVENT_TRIGGERED);
|
|
assertCalled("channelWritabilityChanged", MASK_CHANNEL_WRITABILITY_CHANGED);
|
|
assertCalled("exceptionCaught", MASK_EXCEPTION_CAUGHT);
|
|
}
|
|
|
|
private void assertCalled(String methodName, int mask) {
|
|
assertTrue(methodName + " was not called", (executionMask & mask) != 0);
|
|
}
|
|
}
|
|
|
|
OutboundCalledHandler outboundCalledHandler = new OutboundCalledHandler();
|
|
SkipHandler skipHandler = new SkipHandler();
|
|
InboundCalledHandler inboundCalledHandler = new InboundCalledHandler();
|
|
pipeline.addLast(outboundCalledHandler, skipHandler, inboundCalledHandler);
|
|
|
|
pipeline.fireChannelRegistered();
|
|
pipeline.fireChannelUnregistered();
|
|
pipeline.fireChannelActive();
|
|
pipeline.fireChannelInactive();
|
|
pipeline.fireChannelRead("");
|
|
pipeline.fireChannelReadComplete();
|
|
pipeline.fireChannelWritabilityChanged();
|
|
pipeline.fireUserEventTriggered("");
|
|
pipeline.fireExceptionCaught(new Exception());
|
|
|
|
pipeline.register().syncUninterruptibly();
|
|
pipeline.deregister().syncUninterruptibly();
|
|
pipeline.bind(new SocketAddress() {
|
|
}).syncUninterruptibly();
|
|
pipeline.connect(new SocketAddress() {
|
|
}).syncUninterruptibly();
|
|
pipeline.disconnect().syncUninterruptibly();
|
|
pipeline.close().syncUninterruptibly();
|
|
pipeline.write("");
|
|
pipeline.flush();
|
|
pipeline.read();
|
|
|
|
pipeline.remove(outboundCalledHandler);
|
|
pipeline.remove(inboundCalledHandler);
|
|
pipeline.remove(skipHandler);
|
|
|
|
assertFalse(channel.finish());
|
|
|
|
outboundCalledHandler.assertCalled();
|
|
inboundCalledHandler.assertCalled();
|
|
skipHandler.assertSkipped();
|
|
}
|
|
|
|
@Test
|
|
public void testWriteThrowsReleaseMessage() {
|
|
testWriteThrowsReleaseMessage0(false);
|
|
}
|
|
|
|
@Test
|
|
public void testWriteAndFlushThrowsReleaseMessage() {
|
|
testWriteThrowsReleaseMessage0(true);
|
|
}
|
|
|
|
private void testWriteThrowsReleaseMessage0(boolean flush) {
|
|
ReferenceCounted referenceCounted = new AbstractReferenceCounted() {
|
|
@Override
|
|
protected void deallocate() {
|
|
// NOOP
|
|
}
|
|
|
|
@Override
|
|
public ReferenceCounted touch(Object hint) {
|
|
return this;
|
|
}
|
|
};
|
|
assertEquals(1, referenceCounted.refCnt());
|
|
|
|
Channel channel = new LocalChannel(group.next());
|
|
Channel channel2 = new LocalChannel(group.next());
|
|
channel.register().syncUninterruptibly();
|
|
channel2.register().syncUninterruptibly();
|
|
|
|
try {
|
|
if (flush) {
|
|
channel.writeAndFlush(referenceCounted, channel2.newPromise());
|
|
} else {
|
|
channel.write(referenceCounted, channel2.newPromise());
|
|
}
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
// expected
|
|
}
|
|
assertEquals(0, referenceCounted.refCnt());
|
|
|
|
channel.close().syncUninterruptibly();
|
|
channel2.close().syncUninterruptibly();
|
|
}
|
|
|
|
@Test(timeout = 5000)
|
|
public void handlerAddedStateUpdatedBeforeHandlerAddedDoneForceEventLoop() throws InterruptedException {
|
|
handlerAddedStateUpdatedBeforeHandlerAddedDone(true);
|
|
}
|
|
|
|
@Test(timeout = 5000)
|
|
public void handlerAddedStateUpdatedBeforeHandlerAddedDoneOnCallingThread() throws InterruptedException {
|
|
handlerAddedStateUpdatedBeforeHandlerAddedDone(false);
|
|
}
|
|
|
|
private static void handlerAddedStateUpdatedBeforeHandlerAddedDone(boolean executeInEventLoop)
|
|
throws InterruptedException {
|
|
final ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
final Object userEvent = new Object();
|
|
final Object writeObject = new Object();
|
|
final CountDownLatch doneLatch = new CountDownLatch(1);
|
|
|
|
Runnable r = () -> {
|
|
pipeline.addLast(new ChannelHandler() {
|
|
@Override
|
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
|
if (evt == userEvent) {
|
|
ctx.write(writeObject);
|
|
}
|
|
ctx.fireUserEventTriggered(evt);
|
|
}
|
|
});
|
|
pipeline.addFirst(new ChannelHandler() {
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
ctx.fireUserEventTriggered(userEvent);
|
|
}
|
|
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
if (msg == writeObject) {
|
|
doneLatch.countDown();
|
|
}
|
|
ctx.write(msg, promise);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (executeInEventLoop) {
|
|
pipeline.channel().eventLoop().execute(r);
|
|
} else {
|
|
r.run();
|
|
}
|
|
|
|
doneLatch.await();
|
|
}
|
|
|
|
private static final class TestTask implements Runnable {
|
|
|
|
private final ChannelPipeline pipeline;
|
|
private final CountDownLatch latch;
|
|
|
|
TestTask(ChannelPipeline pipeline, CountDownLatch latch) {
|
|
this.pipeline = pipeline;
|
|
this.latch = latch;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
pipeline.addLast(new ChannelHandler() { });
|
|
latch.countDown();
|
|
}
|
|
}
|
|
|
|
private static final class CallbackCheckHandler extends ChannelHandlerAdapter {
|
|
final Promise<Boolean> addedHandler = ImmediateEventExecutor.INSTANCE.newPromise();
|
|
final Promise<Boolean> removedHandler = ImmediateEventExecutor.INSTANCE.newPromise();
|
|
final AtomicReference<Throwable> error = new AtomicReference<>();
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
if (!addedHandler.trySuccess(true)) {
|
|
error.set(new AssertionError("handlerAdded(...) called multiple times: " + ctx.name()));
|
|
} else if (removedHandler.getNow() == Boolean.TRUE) {
|
|
error.set(new AssertionError("handlerRemoved(...) called before handlerAdded(...): " + ctx.name()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
if (!removedHandler.trySuccess(true)) {
|
|
error.set(new AssertionError("handlerRemoved(...) called multiple times: " + ctx.name()));
|
|
} else if (addedHandler.getNow() == Boolean.FALSE) {
|
|
error.set(new AssertionError("handlerRemoved(...) called before handlerAdded(...): " + ctx.name()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class CheckExceptionHandler implements ChannelInboundHandler {
|
|
private final Throwable expected;
|
|
private final Promise<Void> promise;
|
|
|
|
CheckExceptionHandler(Throwable expected, Promise<Void> promise) {
|
|
this.expected = expected;
|
|
this.promise = promise;
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
if (cause instanceof ChannelPipelineException && cause.getCause() == expected) {
|
|
promise.setSuccess(null);
|
|
} else {
|
|
promise.setFailure(new AssertionError("cause not the expected instance"));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void assertHandler(CheckOrderHandler actual, CheckOrderHandler... handlers) throws Throwable {
|
|
for (CheckOrderHandler h : handlers) {
|
|
if (h == actual) {
|
|
actual.checkError();
|
|
return;
|
|
}
|
|
}
|
|
fail("handler was not one of the expected handlers");
|
|
}
|
|
|
|
private static final class CheckOrderHandler implements ChannelInboundHandler {
|
|
private final Queue<CheckOrderHandler> addedQueue;
|
|
private final Queue<CheckOrderHandler> removedQueue;
|
|
private final AtomicReference<Throwable> error = new AtomicReference<>();
|
|
|
|
CheckOrderHandler(Queue<CheckOrderHandler> addedQueue, Queue<CheckOrderHandler> removedQueue) {
|
|
this.addedQueue = addedQueue;
|
|
this.removedQueue = removedQueue;
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
addedQueue.add(this);
|
|
checkExecutor(ctx);
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
removedQueue.add(this);
|
|
checkExecutor(ctx);
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
error.set(cause);
|
|
}
|
|
|
|
void checkError() throws Throwable {
|
|
Throwable cause = error.get();
|
|
if (cause != null) {
|
|
throw cause;
|
|
}
|
|
}
|
|
|
|
private void checkExecutor(ChannelHandlerContext ctx) {
|
|
if (!ctx.executor().inEventLoop()) {
|
|
error.set(new AssertionError());
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class CheckEventExecutorHandler extends ChannelHandlerAdapter {
|
|
final EventExecutor executor;
|
|
final Promise<Void> addedPromise;
|
|
final Promise<Void> removedPromise;
|
|
|
|
CheckEventExecutorHandler(EventExecutor executor) {
|
|
this.executor = executor;
|
|
addedPromise = executor.newPromise();
|
|
removedPromise = executor.newPromise();
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
assertExecutor(ctx, addedPromise);
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
assertExecutor(ctx, removedPromise);
|
|
}
|
|
|
|
private void assertExecutor(ChannelHandlerContext ctx, Promise<Void> promise) {
|
|
final boolean same;
|
|
try {
|
|
same = executor == ctx.executor();
|
|
} catch (Throwable cause) {
|
|
promise.setFailure(cause);
|
|
return;
|
|
}
|
|
if (same) {
|
|
promise.setSuccess(null);
|
|
} else {
|
|
promise.setFailure(new AssertionError("EventExecutor not the same"));
|
|
}
|
|
}
|
|
}
|
|
private static final class ErrorChannelHandler extends ChannelHandlerAdapter {
|
|
private final AtomicReference<Throwable> error;
|
|
|
|
ErrorChannelHandler(AtomicReference<Throwable> error) {
|
|
this.error = error;
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
error.set(new AssertionError());
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
error.set(new AssertionError());
|
|
}
|
|
}
|
|
|
|
private static int next(DefaultChannelHandlerContext ctx) {
|
|
DefaultChannelHandlerContext next = ctx.next;
|
|
if (next == null) {
|
|
return Integer.MAX_VALUE;
|
|
}
|
|
|
|
return toInt(next.name());
|
|
}
|
|
|
|
private static int toInt(String name) {
|
|
try {
|
|
return Integer.parseInt(name);
|
|
} catch (NumberFormatException e) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private static void verifyContextNumber(ChannelPipeline pipeline, int expectedNumber) {
|
|
assertEquals(expectedNumber, pipeline.names().size());
|
|
assertEquals(expectedNumber, pipeline.toMap().size());
|
|
|
|
pipeline.executor().submit(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
DefaultChannelHandlerContext ctx = (DefaultChannelHandlerContext) pipeline.firstContext();
|
|
int handlerNumber = 0;
|
|
if (ctx != null) {
|
|
for (;;) {
|
|
handlerNumber++;
|
|
if (ctx == pipeline.lastContext()) {
|
|
break;
|
|
}
|
|
ctx = ctx.next;
|
|
}
|
|
}
|
|
assertEquals(expectedNumber, handlerNumber);
|
|
}
|
|
}).syncUninterruptibly();
|
|
}
|
|
|
|
private static ChannelHandler[] newHandlers(int num) {
|
|
assert num > 0;
|
|
|
|
ChannelHandler[] handlers = new ChannelHandler[num];
|
|
for (int i = 0; i < num; i++) {
|
|
handlers[i] = newHandler();
|
|
}
|
|
|
|
return handlers;
|
|
}
|
|
|
|
private static ChannelHandler newHandler() {
|
|
return new TestHandler();
|
|
}
|
|
|
|
@Sharable
|
|
private static class TestHandler implements ChannelHandler { }
|
|
|
|
private static class BufferedTestHandler implements ChannelHandler {
|
|
final Queue<Object> inboundBuffer = new ArrayDeque<>();
|
|
final Queue<Object> outboundBuffer = new ArrayDeque<>();
|
|
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
outboundBuffer.add(msg);
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
inboundBuffer.add(msg);
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
if (!inboundBuffer.isEmpty()) {
|
|
for (Object o: inboundBuffer) {
|
|
ctx.fireChannelRead(o);
|
|
}
|
|
ctx.fireChannelReadComplete();
|
|
}
|
|
if (!outboundBuffer.isEmpty()) {
|
|
for (Object o: outboundBuffer) {
|
|
ctx.write(o);
|
|
}
|
|
ctx.flush();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Test handler to validate life-cycle aware behavior. */
|
|
private static final class LifeCycleAwareTestHandler extends ChannelHandlerAdapter {
|
|
private final String name;
|
|
|
|
private boolean afterAdd;
|
|
private boolean afterRemove;
|
|
|
|
/**
|
|
* Constructs life-cycle aware test handler.
|
|
*
|
|
* @param name Handler name to display in assertion messages.
|
|
*/
|
|
private LifeCycleAwareTestHandler(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public void validate(boolean afterAdd, boolean afterRemove) {
|
|
assertEquals(name, afterAdd, this.afterAdd);
|
|
assertEquals(name, afterRemove, this.afterRemove);
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
validate(false, false);
|
|
|
|
afterAdd = true;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
validate(true, false);
|
|
|
|
afterRemove = true;
|
|
}
|
|
}
|
|
|
|
private static final class WrapperExecutor extends AbstractEventExecutor {
|
|
|
|
private final ExecutorService wrapped = Executors.newSingleThreadExecutor();
|
|
|
|
@Override
|
|
public boolean isShuttingDown() {
|
|
return wrapped.isShutdown();
|
|
}
|
|
|
|
@Override
|
|
public Future<?> shutdownGracefully(long l, long l2, TimeUnit timeUnit) {
|
|
throw new IllegalStateException();
|
|
}
|
|
|
|
@Override
|
|
public Future<?> terminationFuture() {
|
|
throw new IllegalStateException();
|
|
}
|
|
|
|
@Override
|
|
public void shutdown() {
|
|
wrapped.shutdown();
|
|
}
|
|
|
|
@Override
|
|
public List<Runnable> shutdownNow() {
|
|
return wrapped.shutdownNow();
|
|
}
|
|
|
|
@Override
|
|
public boolean isShutdown() {
|
|
return wrapped.isShutdown();
|
|
}
|
|
|
|
@Override
|
|
public boolean isTerminated() {
|
|
return wrapped.isTerminated();
|
|
}
|
|
|
|
@Override
|
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
|
return wrapped.awaitTermination(timeout, unit);
|
|
}
|
|
|
|
@Override
|
|
public boolean inEventLoop(Thread thread) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void execute(Runnable command) {
|
|
wrapped.execute(command);
|
|
}
|
|
|
|
@Override
|
|
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public ScheduledFuture<?> scheduleWithFixedDelay(
|
|
Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
}
|
|
}
|