1755 lines
62 KiB
Java
1755 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:
|
|
*
|
|
* https://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.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.EventExecutor;
|
|
import io.netty.util.concurrent.Future;
|
|
import io.netty.util.concurrent.ImmediateEventExecutor;
|
|
import io.netty.util.concurrent.Promise;
|
|
import org.hamcrest.Matchers;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.AfterEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Timeout;
|
|
|
|
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.CompletionException;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
import static org.hamcrest.MatcherAssert.assertThat;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assertions.fail;
|
|
|
|
public class DefaultChannelPipelineTest {
|
|
|
|
private static final EventLoopGroup group = new MultithreadEventLoopGroup(1, LocalHandler.newFactory());
|
|
|
|
private Channel self;
|
|
private Channel peer;
|
|
|
|
@AfterAll
|
|
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);
|
|
}
|
|
});
|
|
|
|
Channel channel = sb.bind(LocalAddress.ANY).get();
|
|
|
|
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(channel.localAddress()).get();
|
|
peer = peerRef.get();
|
|
|
|
channel.close().sync();
|
|
}
|
|
|
|
@AfterEach
|
|
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 ChannelHandler {
|
|
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 testAddLastVarArgsSkipsNull() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
pipeline.addLast(null, newHandler(), null);
|
|
assertEquals(1, pipeline.names().size());
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#0", pipeline.names().get(0));
|
|
|
|
pipeline.addLast(newHandler(), null, newHandler());
|
|
assertEquals(3, pipeline.names().size());
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#0", pipeline.names().get(0));
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#1", pipeline.names().get(1));
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#2", pipeline.names().get(2));
|
|
|
|
pipeline.addLast((ChannelHandler) null);
|
|
assertEquals(3, pipeline.names().size());
|
|
}
|
|
|
|
@Test
|
|
public void testAddFirstVarArgsSkipsNull() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
pipeline.addFirst(null, newHandler(), null);
|
|
assertEquals(1, pipeline.names().size());
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#0", pipeline.names().get(0));
|
|
|
|
pipeline.addFirst(newHandler(), null, newHandler());
|
|
assertEquals(3, pipeline.names().size());
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#2", pipeline.names().get(0));
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#1", pipeline.names().get(1));
|
|
assertEquals("DefaultChannelPipelineTest$TestHandler#0", pipeline.names().get(2));
|
|
|
|
pipeline.addFirst((ChannelHandler) null);
|
|
assertEquals(3, pipeline.names().size());
|
|
}
|
|
|
|
@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
|
|
public void testRemoveThrowNoSuchElementException() {
|
|
DefaultChannelPipeline pipeline = new DefaultChannelPipeline(newLocalChannel());
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
|
|
assertThrows(NoSuchElementException.class, () -> 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 testReplaceHandlerChecksDuplicateNames() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
ChannelHandler handler2 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
pipeline.addLast("handler2", handler2);
|
|
|
|
ChannelHandler newHandler1 = newHandler();
|
|
assertThrows(IllegalArgumentException.class, () -> pipeline.replace("handler1", "handler2", newHandler1));
|
|
}
|
|
|
|
@Test
|
|
public void testReplaceNameWithGenerated() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
|
|
ChannelHandler handler1 = newHandler();
|
|
pipeline.addLast("handler1", handler1);
|
|
assertSame(pipeline.get("handler1"), handler1);
|
|
|
|
ChannelHandler newHandler1 = newHandler();
|
|
pipeline.replace("handler1", null, newHandler1);
|
|
assertSame(pipeline.get("DefaultChannelPipelineTest$TestHandler#0"), newHandler1);
|
|
assertNull(pipeline.get("handler1"));
|
|
}
|
|
|
|
@Test
|
|
public void testRenameChannelHandler() {
|
|
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", "newHandler1", newHandler1);
|
|
assertSame(pipeline.get("newHandler1"), newHandler1);
|
|
assertNull(pipeline.get("handler1"));
|
|
|
|
ChannelHandler newHandler3 = newHandler();
|
|
pipeline.replace("handler3", "newHandler3", newHandler3);
|
|
assertSame(pipeline.get("newHandler3"), newHandler3);
|
|
assertNull(pipeline.get("handler3"));
|
|
|
|
ChannelHandler newHandler2 = newHandler();
|
|
pipeline.replace("handler2", "newHandler2", newHandler2);
|
|
assertSame(pipeline.get("newHandler2"), newHandler2);
|
|
assertNull(pipeline.get("handler2"));
|
|
}
|
|
|
|
@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
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
public void testThrowInExceptionCaught() throws InterruptedException {
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
final AtomicInteger counter = new AtomicInteger();
|
|
Channel channel = newLocalChannel();
|
|
try {
|
|
channel.register().syncUninterruptibly();
|
|
channel.pipeline().addLast(new ChannelHandler() {
|
|
class TestException extends Exception { }
|
|
|
|
@Override
|
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
throw new TestException();
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
if (cause instanceof TestException) {
|
|
ctx.executor().execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
latch.countDown();
|
|
}
|
|
});
|
|
}
|
|
counter.incrementAndGet();
|
|
throw new Exception();
|
|
}
|
|
});
|
|
|
|
channel.pipeline().fireChannelReadComplete();
|
|
latch.await();
|
|
assertEquals(1, counter.get());
|
|
} finally {
|
|
channel.close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
public void testThrowInOtherHandlerAfterInvokedFromExceptionCaught() throws InterruptedException {
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
final AtomicInteger counter = new AtomicInteger();
|
|
Channel channel = newLocalChannel();
|
|
try {
|
|
channel.register().syncUninterruptibly();
|
|
channel.pipeline().addLast(new ChannelHandler() {
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
ctx.fireChannelReadComplete();
|
|
}
|
|
}, new ChannelHandler() {
|
|
class TestException extends Exception { }
|
|
|
|
@Override
|
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
throw new TestException();
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
if (cause instanceof TestException) {
|
|
ctx.executor().execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
latch.countDown();
|
|
}
|
|
});
|
|
}
|
|
counter.incrementAndGet();
|
|
throw new Exception();
|
|
}
|
|
});
|
|
|
|
channel.pipeline().fireExceptionCaught(new Exception());
|
|
latch.await();
|
|
assertEquals(1, counter.get());
|
|
} finally {
|
|
channel.close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
@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(value = 10000, unit = TimeUnit.MILLISECONDS)
|
|
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.executor().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.executor().execute(() -> {
|
|
// Validate handler life-cycle methods called.
|
|
handler.validate(true, true);
|
|
removeLatch.countDown();
|
|
});
|
|
}
|
|
removeLatch.await();
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 100000, unit = TimeUnit.MILLISECONDS)
|
|
public void testRemoveAndForwardInbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1, handler2);
|
|
|
|
self.executor().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(value = 10000, unit = TimeUnit.MILLISECONDS)
|
|
public void testRemoveAndForwardOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1, handler2);
|
|
|
|
self.executor().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(value = 10000, unit = TimeUnit.MILLISECONDS)
|
|
public void testReplaceAndForwardOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1);
|
|
|
|
self.executor().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(value = 10000, unit = TimeUnit.MILLISECONDS)
|
|
public void testReplaceAndForwardInboundAndOutbound() throws Exception {
|
|
final BufferedTestHandler handler1 = new BufferedTestHandler();
|
|
final BufferedTestHandler handler2 = new BufferedTestHandler();
|
|
|
|
setUp(handler1);
|
|
|
|
self.executor().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(value = 10000, unit = TimeUnit.MILLISECONDS)
|
|
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.executor().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();
|
|
}
|
|
|
|
@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(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
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(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
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(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
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(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
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());
|
|
|
|
CallbackCheckHandler handlerType = new CallbackCheckHandler();
|
|
pipeline.addFirst(handlerType);
|
|
pipeline.remove(handlerType.getClass());
|
|
assertTrue(handlerType.addedHandler.get());
|
|
assertTrue(handlerType.removedHandler.get());
|
|
|
|
CallbackCheckHandler handlerName = new CallbackCheckHandler();
|
|
pipeline.addFirst("handler", handlerName);
|
|
pipeline.remove("handler");
|
|
assertTrue(handlerName.addedHandler.get());
|
|
assertTrue(handlerName.removedHandler.get());
|
|
|
|
CallbackCheckHandler first = new CallbackCheckHandler();
|
|
pipeline.addFirst(first);
|
|
pipeline.removeFirst();
|
|
assertTrue(first.addedHandler.get());
|
|
assertTrue(first.removedHandler.get());
|
|
|
|
CallbackCheckHandler last = new CallbackCheckHandler();
|
|
pipeline.addFirst(last);
|
|
pipeline.removeLast();
|
|
assertTrue(last.addedHandler.get());
|
|
assertTrue(last.removedHandler.get());
|
|
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
Throwable cause = handler.error.get();
|
|
Throwable causeName = handlerName.error.get();
|
|
Throwable causeType = handlerType.error.get();
|
|
Throwable causeFirst = first.error.get();
|
|
Throwable causeLast = last.error.get();
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
rethrowIfNotNull(cause);
|
|
rethrowIfNotNull(causeName);
|
|
rethrowIfNotNull(causeType);
|
|
rethrowIfNotNull(causeFirst);
|
|
rethrowIfNotNull(causeLast);
|
|
}
|
|
|
|
private static void rethrowIfNotNull(Throwable cause) throws Throwable {
|
|
if (cause != null) {
|
|
throw cause;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testOperationsFailWhenRemoved() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
try {
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
|
|
ChannelHandler handler = new ChannelHandler() { };
|
|
pipeline.addFirst(handler);
|
|
ChannelHandlerContext ctx = pipeline.context(handler);
|
|
pipeline.remove(handler);
|
|
|
|
testOperationsFailsOnContext(ctx);
|
|
} finally {
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testOperationsFailWhenReplaced() {
|
|
ChannelPipeline pipeline = newLocalChannel().pipeline();
|
|
try {
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
|
|
ChannelHandler handler = new ChannelHandler() { };
|
|
pipeline.addFirst(handler);
|
|
ChannelHandlerContext ctx = pipeline.context(handler);
|
|
pipeline.replace(handler, null, new ChannelHandler() { });
|
|
|
|
testOperationsFailsOnContext(ctx);
|
|
} finally {
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
private static void testOperationsFailsOnContext(ChannelHandlerContext ctx) {
|
|
assertChannelPipelineException(ctx.writeAndFlush(""));
|
|
assertChannelPipelineException(ctx.write(""));
|
|
assertChannelPipelineException(ctx.bind(new SocketAddress() { }));
|
|
assertChannelPipelineException(ctx.close());
|
|
assertChannelPipelineException(ctx.connect(new SocketAddress() { }));
|
|
assertChannelPipelineException(ctx.connect(new SocketAddress() { }, new SocketAddress() { }));
|
|
assertChannelPipelineException(ctx.deregister());
|
|
assertChannelPipelineException(ctx.disconnect());
|
|
|
|
class ChannelPipelineExceptionValidator implements ChannelHandler {
|
|
|
|
private Promise<Void> validationPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
try {
|
|
assertThat(cause, Matchers.instanceOf(ChannelPipelineException.class));
|
|
} catch (Throwable error) {
|
|
validationPromise.setFailure(error);
|
|
return;
|
|
}
|
|
validationPromise.setSuccess(null);
|
|
}
|
|
|
|
void validate() {
|
|
validationPromise.syncUninterruptibly();
|
|
validationPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
|
}
|
|
}
|
|
|
|
ChannelPipelineExceptionValidator validator = new ChannelPipelineExceptionValidator();
|
|
ctx.pipeline().addLast(validator);
|
|
|
|
ctx.fireChannelRead("");
|
|
validator.validate();
|
|
|
|
ctx.fireUserEventTriggered("");
|
|
validator.validate();
|
|
|
|
ctx.fireChannelReadComplete();
|
|
validator.validate();
|
|
|
|
ctx.fireExceptionCaught(new Exception());
|
|
validator.validate();
|
|
|
|
ctx.fireChannelActive();
|
|
validator.validate();
|
|
|
|
ctx.fireChannelRegistered();
|
|
validator.validate();
|
|
|
|
ctx.fireChannelInactive();
|
|
validator.validate();
|
|
|
|
ctx.fireChannelUnregistered();
|
|
validator.validate();
|
|
|
|
ctx.fireChannelWritabilityChanged();
|
|
validator.validate();
|
|
}
|
|
|
|
private static void assertChannelPipelineException(Future<Void> f) {
|
|
try {
|
|
f.syncUninterruptibly();
|
|
} catch (CompletionException e) {
|
|
assertThat(e.getCause(), Matchers.instanceOf(ChannelPipelineException.class));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
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());
|
|
assertFalse(handler2.removedHandler.isDone());
|
|
|
|
pipeline.channel().register().syncUninterruptibly();
|
|
Throwable cause = handler.error.get();
|
|
if (cause != null) {
|
|
throw cause;
|
|
}
|
|
|
|
Throwable cause2 = handler2.error.get();
|
|
if (cause2 != null) {
|
|
throw cause2;
|
|
}
|
|
|
|
assertFalse(handler2.removedHandler.isDone());
|
|
pipeline.remove(handler2);
|
|
assertTrue(handler2.removedHandler.get());
|
|
pipeline.channel().close().syncUninterruptibly();
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
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(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
public void testAddInListenerNio() throws Throwable {
|
|
EventLoopGroup nioEventLoopGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
|
|
try {
|
|
testAddInListener(new NioSocketChannel(nioEventLoopGroup.next()));
|
|
} finally {
|
|
nioEventLoopGroup.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
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(channel, (ch, future) -> {
|
|
ChannelPipeline pipeline = ch.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 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().executor().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 ChannelHandler {
|
|
private int state = 2;
|
|
private Error errorRef;
|
|
|
|
private void fail() {
|
|
errorRef = new AssertionError("Method should never been called");
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> bind(ChannelHandlerContext ctx, SocketAddress localAddress) {
|
|
fail();
|
|
return ctx.bind(localAddress);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
|
SocketAddress localAddress) {
|
|
fail();
|
|
return ctx.connect(remoteAddress, localAddress);
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> disconnect(ChannelHandlerContext ctx) {
|
|
fail();
|
|
return ctx.disconnect();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> close(ChannelHandlerContext ctx) {
|
|
fail();
|
|
return ctx.close();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> register(ChannelHandlerContext ctx) {
|
|
fail();
|
|
return ctx.register();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> deregister(ChannelHandlerContext ctx) {
|
|
fail();
|
|
return ctx.deregister();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public void read(ChannelHandlerContext ctx) {
|
|
fail();
|
|
ctx.read();
|
|
}
|
|
|
|
@Skip
|
|
@Override
|
|
public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
|
|
fail();
|
|
return ctx.write(msg);
|
|
}
|
|
|
|
@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 ChannelHandler {
|
|
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 Future<Void> bind(ChannelHandlerContext ctx, SocketAddress localAddress) {
|
|
executionMask |= MASK_BIND;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
|
SocketAddress localAddress) {
|
|
executionMask |= MASK_CONNECT;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> disconnect(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_DISCONNECT;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> close(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_CLOSE;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> register(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_REGISTER;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> deregister(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_DEREGISTER;
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public void read(ChannelHandlerContext ctx) {
|
|
executionMask |= MASK_READ;
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
|
|
executionMask |= MASK_WRITE;
|
|
ReferenceCountUtil.release(msg);
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@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((executionMask & mask) != 0, methodName + " was not called");
|
|
}
|
|
}
|
|
|
|
final class InboundCalledHandler implements ChannelHandler {
|
|
|
|
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((executionMask & mask) != 0, methodName + " was not called");
|
|
}
|
|
}
|
|
|
|
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
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void handlerAddedStateUpdatedBeforeHandlerAddedDoneForceEventLoop() throws InterruptedException {
|
|
handlerAddedStateUpdatedBeforeHandlerAddedDone(true);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
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 Future<Void> write(ChannelHandlerContext ctx, Object msg) {
|
|
if (msg == writeObject) {
|
|
doneLatch.countDown();
|
|
}
|
|
return ctx.write(msg);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (executeInEventLoop) {
|
|
pipeline.channel().executor().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.isDone() && 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.isDone() && addedHandler.getNow() == Boolean.FALSE) {
|
|
error.set(new AssertionError("handlerRemoved(...) called before handlerAdded(...): " + ctx.name()));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 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(() -> {
|
|
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 Future<Void> write(ChannelHandlerContext ctx, Object msg) {
|
|
outboundBuffer.add(msg);
|
|
return ctx.newSucceededFuture();
|
|
}
|
|
|
|
@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(afterAdd, this.afterAdd, name);
|
|
assertEquals(afterRemove, this.afterRemove, name);
|
|
}
|
|
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
|
validate(false, false);
|
|
|
|
afterAdd = true;
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
|
validate(true, false);
|
|
|
|
afterRemove = true;
|
|
}
|
|
}
|
|
}
|