328 lines
12 KiB
Java
328 lines
12 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.local;
|
|
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
import io.netty.channel.Channel;
|
|
import io.netty.channel.ChannelDuplexHandler;
|
|
import io.netty.channel.ChannelHandler;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
import io.netty.channel.ChannelInitializer;
|
|
import io.netty.channel.ChannelPromise;
|
|
import io.netty.channel.EventLoopGroup;
|
|
import io.netty.util.ReferenceCountUtil;
|
|
import io.netty.util.concurrent.DefaultEventExecutorGroup;
|
|
import io.netty.util.concurrent.DefaultThreadFactory;
|
|
import io.netty.util.concurrent.EventExecutorGroup;
|
|
import org.junit.AfterClass;
|
|
import org.junit.Assert;
|
|
import org.junit.BeforeClass;
|
|
import org.junit.Ignore;
|
|
import org.junit.Test;
|
|
|
|
import java.util.Deque;
|
|
import java.util.LinkedList;
|
|
import java.util.Queue;
|
|
import java.util.Random;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
|
|
public class LocalTransportThreadModelTest3 {
|
|
|
|
enum EventType {
|
|
EXCEPTION_CAUGHT,
|
|
USER_EVENT,
|
|
MESSAGE_RECEIVED_LAST,
|
|
INACTIVE,
|
|
ACTIVE,
|
|
UNREGISTERED,
|
|
REGISTERED,
|
|
MESSAGE_RECEIVED,
|
|
WRITE,
|
|
READ
|
|
}
|
|
|
|
private static EventLoopGroup group;
|
|
private static LocalAddress localAddr;
|
|
|
|
@BeforeClass
|
|
public static void init() {
|
|
// Configure a test server
|
|
group = new LocalEventLoopGroup();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
sb.group(group)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
// Discard
|
|
ReferenceCountUtil.release(msg);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
localAddr = (LocalAddress) sb.bind(LocalAddress.ANY).syncUninterruptibly().channel().localAddress();
|
|
}
|
|
|
|
@AfterClass
|
|
public static void destroy() throws Exception {
|
|
group.shutdownGracefully().sync();
|
|
}
|
|
|
|
@Test(timeout = 60000)
|
|
@Ignore("regression test")
|
|
public void testConcurrentAddRemoveInboundEventsMultiple() throws Throwable {
|
|
for (int i = 0; i < 50; i ++) {
|
|
testConcurrentAddRemoveInboundEvents();
|
|
}
|
|
}
|
|
|
|
@Test(timeout = 60000)
|
|
@Ignore("regression test")
|
|
public void testConcurrentAddRemoveOutboundEventsMultiple() throws Throwable {
|
|
for (int i = 0; i < 50; i ++) {
|
|
testConcurrentAddRemoveOutboundEvents();
|
|
}
|
|
}
|
|
|
|
@Test(timeout = 30000)
|
|
@Ignore("needs a fix")
|
|
public void testConcurrentAddRemoveInboundEvents() throws Throwable {
|
|
testConcurrentAddRemove(true);
|
|
}
|
|
|
|
@Test(timeout = 30000)
|
|
@Ignore("needs a fix")
|
|
public void testConcurrentAddRemoveOutboundEvents() throws Throwable {
|
|
testConcurrentAddRemove(false);
|
|
}
|
|
|
|
private static void testConcurrentAddRemove(boolean inbound) throws Exception {
|
|
EventLoopGroup l = new LocalEventLoopGroup(4, new DefaultThreadFactory("l"));
|
|
EventExecutorGroup e1 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e1"));
|
|
EventExecutorGroup e2 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e2"));
|
|
EventExecutorGroup e3 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e3"));
|
|
EventExecutorGroup e4 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e4"));
|
|
EventExecutorGroup e5 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e5"));
|
|
|
|
final EventExecutorGroup[] groups = {e1, e2, e3, e4, e5};
|
|
try {
|
|
Deque<EventType> events = new ConcurrentLinkedDeque<EventType>();
|
|
final EventForwarder h1 = new EventForwarder();
|
|
final EventForwarder h2 = new EventForwarder();
|
|
final EventForwarder h3 = new EventForwarder();
|
|
final EventForwarder h4 = new EventForwarder();
|
|
final EventForwarder h5 = new EventForwarder();
|
|
final EventRecorder h6 = new EventRecorder(events, inbound);
|
|
|
|
final Channel ch = new LocalChannel();
|
|
if (!inbound) {
|
|
ch.config().setAutoRead(false);
|
|
}
|
|
ch.pipeline().addLast(e1, h1)
|
|
.addLast(e1, h2)
|
|
.addLast(e1, h3)
|
|
.addLast(e1, h4)
|
|
.addLast(e1, h5)
|
|
.addLast(e1, "recorder", h6);
|
|
|
|
l.register(ch).sync().channel().connect(localAddr).sync();
|
|
|
|
final LinkedList<EventType> expectedEvents = events(inbound, 8192);
|
|
|
|
Throwable cause = new Throwable();
|
|
|
|
Thread pipelineModifier = new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Random random = new Random();
|
|
|
|
while (true) {
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (InterruptedException e) {
|
|
return;
|
|
}
|
|
if (!ch.isRegistered()) {
|
|
continue;
|
|
}
|
|
//EventForwardHandler forwardHandler = forwarders[random.nextInt(forwarders.length)];
|
|
ChannelHandler handler = ch.pipeline().removeFirst();
|
|
ch.pipeline().addBefore(groups[random.nextInt(groups.length)], "recorder",
|
|
UUID.randomUUID().toString(), handler);
|
|
}
|
|
}
|
|
});
|
|
pipelineModifier.setDaemon(true);
|
|
pipelineModifier.start();
|
|
for (EventType event: expectedEvents) {
|
|
switch (event) {
|
|
case EXCEPTION_CAUGHT:
|
|
ch.pipeline().fireExceptionCaught(cause);
|
|
break;
|
|
case MESSAGE_RECEIVED:
|
|
ch.pipeline().fireChannelRead("");
|
|
break;
|
|
case MESSAGE_RECEIVED_LAST:
|
|
ch.pipeline().fireChannelReadComplete();
|
|
break;
|
|
case USER_EVENT:
|
|
ch.pipeline().fireUserEventTriggered("");
|
|
break;
|
|
case WRITE:
|
|
ch.pipeline().write("");
|
|
break;
|
|
case READ:
|
|
ch.pipeline().read();
|
|
break;
|
|
}
|
|
}
|
|
|
|
ch.close().sync();
|
|
|
|
while (events.peekLast() != EventType.UNREGISTERED) {
|
|
Thread.sleep(10);
|
|
}
|
|
|
|
expectedEvents.addFirst(EventType.ACTIVE);
|
|
expectedEvents.addFirst(EventType.REGISTERED);
|
|
expectedEvents.addLast(EventType.INACTIVE);
|
|
expectedEvents.addLast(EventType.UNREGISTERED);
|
|
|
|
for (;;) {
|
|
EventType event = events.poll();
|
|
if (event == null) {
|
|
Assert.assertTrue("Missing events:" + expectedEvents.toString(), expectedEvents.isEmpty());
|
|
break;
|
|
}
|
|
Assert.assertEquals(event, expectedEvents.poll());
|
|
}
|
|
} finally {
|
|
l.shutdownGracefully();
|
|
e1.shutdownGracefully();
|
|
e2.shutdownGracefully();
|
|
e3.shutdownGracefully();
|
|
e4.shutdownGracefully();
|
|
e5.shutdownGracefully();
|
|
|
|
l.terminationFuture().sync();
|
|
e1.terminationFuture().sync();
|
|
e2.terminationFuture().sync();
|
|
e3.terminationFuture().sync();
|
|
e4.terminationFuture().sync();
|
|
e5.terminationFuture().sync();
|
|
}
|
|
}
|
|
|
|
private static LinkedList<EventType> events(boolean inbound, int size) {
|
|
EventType[] events;
|
|
if (inbound) {
|
|
events = new EventType[] {
|
|
EventType.USER_EVENT, EventType.MESSAGE_RECEIVED, EventType.MESSAGE_RECEIVED_LAST,
|
|
EventType.EXCEPTION_CAUGHT};
|
|
} else {
|
|
events = new EventType[] {
|
|
EventType.READ, EventType.WRITE, EventType.EXCEPTION_CAUGHT };
|
|
}
|
|
|
|
Random random = new Random();
|
|
LinkedList<EventType> expectedEvents = new LinkedList<EventType>();
|
|
for (int i = 0; i < size; i++) {
|
|
expectedEvents.add(events[random.nextInt(events.length)]);
|
|
}
|
|
return expectedEvents;
|
|
}
|
|
|
|
@ChannelHandler.Sharable
|
|
private static final class EventForwarder extends ChannelDuplexHandler { }
|
|
|
|
private static final class EventRecorder extends ChannelDuplexHandler {
|
|
private final Queue<EventType> events;
|
|
private final boolean inbound;
|
|
|
|
public EventRecorder(Queue<EventType> events, boolean inbound) {
|
|
this.events = events;
|
|
this.inbound = inbound;
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
events.add(EventType.EXCEPTION_CAUGHT);
|
|
}
|
|
|
|
@Override
|
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
if (inbound) {
|
|
events.add(EventType.USER_EVENT);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
if (inbound) {
|
|
events.add(EventType.MESSAGE_RECEIVED_LAST);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
events.add(EventType.INACTIVE);
|
|
}
|
|
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
events.add(EventType.ACTIVE);
|
|
}
|
|
|
|
@Override
|
|
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
|
events.add(EventType.UNREGISTERED);
|
|
}
|
|
|
|
@Override
|
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
events.add(EventType.REGISTERED);
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (inbound) {
|
|
events.add(EventType.MESSAGE_RECEIVED);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
if (!inbound) {
|
|
events.add(EventType.WRITE);
|
|
}
|
|
promise.setSuccess();
|
|
}
|
|
|
|
@Override
|
|
public void read(ChannelHandlerContext ctx) {
|
|
if (!inbound) {
|
|
events.add(EventType.READ);
|
|
}
|
|
}
|
|
}
|
|
}
|