1177 lines
45 KiB
Java
1177 lines
45 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.local;
|
|
|
|
import io.netty.bootstrap.Bootstrap;
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
import io.netty.channel.AbstractChannel;
|
|
import io.netty.channel.Channel;
|
|
import io.netty.channel.ChannelHandler;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.channel.ChannelInitializer;
|
|
import io.netty.channel.ChannelOption;
|
|
import io.netty.channel.EventLoop;
|
|
import io.netty.channel.EventLoopGroup;
|
|
import io.netty.channel.IoHandler;
|
|
import io.netty.channel.MultithreadEventLoopGroup;
|
|
import io.netty.channel.SimpleChannelInboundHandler;
|
|
import io.netty.channel.SingleThreadEventLoop;
|
|
import io.netty.util.ReferenceCountUtil;
|
|
import io.netty.util.concurrent.Future;
|
|
import io.netty.util.concurrent.FutureListener;
|
|
import io.netty.util.concurrent.Promise;
|
|
import io.netty.util.concurrent.RejectedExecutionHandler;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Timeout;
|
|
|
|
import java.net.ConnectException;
|
|
import java.net.SocketAddress;
|
|
import java.nio.channels.ClosedChannelException;
|
|
import java.util.concurrent.CompletionException;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
import static org.hamcrest.CoreMatchers.is;
|
|
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.assertNull;
|
|
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 LocalChannelTest {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(LocalChannelTest.class);
|
|
|
|
private static final LocalAddress TEST_ADDRESS = new LocalAddress("test.id");
|
|
|
|
private static EventLoopGroup group1;
|
|
private static EventLoopGroup group2;
|
|
private static EventLoopGroup sharedGroup;
|
|
|
|
@BeforeAll
|
|
public static void beforeClass() {
|
|
group1 = new MultithreadEventLoopGroup(2, LocalHandler.newFactory());
|
|
group2 = new MultithreadEventLoopGroup(2, LocalHandler.newFactory());
|
|
sharedGroup = new MultithreadEventLoopGroup(1, LocalHandler.newFactory());
|
|
}
|
|
|
|
@AfterAll
|
|
public static void afterClass() throws InterruptedException {
|
|
Future<?> group1Future = group1.shutdownGracefully(0, 0, SECONDS);
|
|
Future<?> group2Future = group2.shutdownGracefully(0, 0, SECONDS);
|
|
Future<?> sharedGroupFuture = sharedGroup.shutdownGracefully(0, 0, SECONDS);
|
|
group1Future.await();
|
|
group2Future.await();
|
|
sharedGroupFuture.await();
|
|
}
|
|
|
|
@Test
|
|
public void testLocalAddressReuse() throws Exception {
|
|
for (int i = 0; i < 2; i ++) {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new TestHandler());
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
final Channel ccCpy = cc;
|
|
cc.executor().execute(() -> {
|
|
// Send a message event up the pipeline.
|
|
ccCpy.pipeline().fireChannelRead("Hello, World");
|
|
latch.countDown();
|
|
});
|
|
assertTrue(latch.await(5, SECONDS));
|
|
|
|
// Close the channel
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
sc.closeFuture().sync();
|
|
|
|
assertNull(LocalChannelRegistry.get(TEST_ADDRESS), String.format(
|
|
"Expected null, got channel '%s' for local address '%s'",
|
|
LocalChannelRegistry.get(TEST_ADDRESS), TEST_ADDRESS));
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testWriteFailsFastOnClosedChannel() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new TestHandler());
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
|
|
// Close the channel and write something.
|
|
cc.close().sync();
|
|
try {
|
|
cc.writeAndFlush(new Object()).sync();
|
|
fail("must raise a ClosedChannelException");
|
|
} catch (CompletionException cause) {
|
|
Throwable e = cause.getCause();
|
|
assertThat(e, is(instanceOf(ClosedChannelException.class)));
|
|
// Ensure that the actual write attempt on a closed channel was never made by asserting that
|
|
// the ClosedChannelException has been created by AbstractUnsafe rather than transport implementations.
|
|
if (e.getStackTrace().length > 0) {
|
|
assertThat(
|
|
e.getStackTrace()[0].getClassName(), is(AbstractChannel.class.getName() +
|
|
"$AbstractUnsafe"));
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testServerCloseChannelSameEventLoop() throws Exception {
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
ServerBootstrap sb = new ServerBootstrap()
|
|
.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new SimpleChannelInboundHandler<Object>() {
|
|
@Override
|
|
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
ctx.close();
|
|
latch.countDown();
|
|
}
|
|
});
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
Bootstrap b = new Bootstrap()
|
|
.group(group2)
|
|
.channel(LocalChannel.class)
|
|
.handler(new SimpleChannelInboundHandler<Object>() {
|
|
@Override
|
|
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
// discard
|
|
}
|
|
});
|
|
cc = b.connect(sc.localAddress()).get();
|
|
cc.writeAndFlush(new Object());
|
|
assertTrue(latch.await(5, SECONDS));
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void localChannelRaceCondition() throws Exception {
|
|
final CountDownLatch closeLatch = new CountDownLatch(1);
|
|
final EventLoopGroup clientGroup = new LocalEventLoopGroup(1) {
|
|
|
|
@Override
|
|
protected EventLoop newChild(
|
|
Executor executor, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler,
|
|
IoHandler ioHandler, int maxTasksPerRun, Object... args) {
|
|
return new SingleThreadEventLoop(executor, ioHandler, maxPendingTasks, rejectedExecutionHandler) {
|
|
|
|
@Override
|
|
protected void run() {
|
|
do {
|
|
runIo();
|
|
Runnable task = pollTask();
|
|
if (task != null) {
|
|
/* Only slow down the anonymous class in LocalChannel#doRegister() */
|
|
if (task.getClass().getEnclosingClass() == LocalChannel.class) {
|
|
try {
|
|
closeLatch.await(1, SECONDS);
|
|
} catch (InterruptedException e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
task.run();
|
|
updateLastExecutionTime();
|
|
}
|
|
} while (!confirmShutdown());
|
|
}
|
|
};
|
|
}
|
|
};
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
sc = sb.group(group2).
|
|
channel(LocalServerChannel.class).
|
|
childHandler(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) throws Exception {
|
|
ch.close();
|
|
closeLatch.countDown();
|
|
}
|
|
}).
|
|
bind(TEST_ADDRESS).get();
|
|
Bootstrap bootstrap = new Bootstrap();
|
|
bootstrap.group(clientGroup).
|
|
channel(LocalChannel.class).
|
|
handler(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) throws Exception {
|
|
/* Do nothing */
|
|
}
|
|
});
|
|
Future<Channel> future = bootstrap.connect(sc.localAddress());
|
|
assertTrue(future.await(2000), "Connection should finish, not time out");
|
|
cc = future.await().isSuccess() ? future.get() : null;
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
clientGroup.shutdownGracefully(0, 0, SECONDS).await();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testReRegister() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new TestHandler());
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
|
|
cc.deregister().syncUninterruptibly();
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCloseInWritePromiseCompletePreservesOrder() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch messageLatch = new CountDownLatch(2);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
|
|
try {
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (msg.equals(data)) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
messageLatch.countDown();
|
|
ctx.fireChannelInactive();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
|
|
final Channel ccCpy = cc;
|
|
// Make sure a write operation is executed in the eventloop
|
|
cc.pipeline().lastContext().executor().execute(() -> {
|
|
ccCpy.writeAndFlush(data.retainedDuplicate())
|
|
.addListener(future -> ccCpy.pipeline().lastContext().close());
|
|
});
|
|
|
|
assertTrue(messageLatch.await(5, SECONDS));
|
|
assertFalse(cc.isOpen());
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
} finally {
|
|
data.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCloseAfterWriteInSameEventLoopPreservesOrder() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch messageLatch = new CountDownLatch(3);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
|
|
try {
|
|
cb.group(sharedGroup)
|
|
.channel(LocalChannel.class)
|
|
.handler(new ChannelHandler() {
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
ctx.writeAndFlush(data.retainedDuplicate());
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data.equals(msg)) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
|
|
sb.group(sharedGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data.equals(msg)) {
|
|
messageLatch.countDown();
|
|
ctx.writeAndFlush(data);
|
|
ctx.close();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
messageLatch.countDown();
|
|
ctx.fireChannelInactive();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
assertTrue(messageLatch.await(5, SECONDS));
|
|
assertFalse(cc.isOpen());
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
} finally {
|
|
data.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testWriteInWritePromiseCompletePreservesOrder() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch messageLatch = new CountDownLatch(2);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
final ByteBuf data2 = Unpooled.wrappedBuffer(new byte[512]);
|
|
|
|
try {
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
final long count = messageLatch.getCount();
|
|
if (data.equals(msg) && count == 2 || data2.equals(msg) && count == 1) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
|
|
final Channel ccCpy = cc;
|
|
// Make sure a write operation is executed in the eventloop
|
|
cc.pipeline().lastContext().executor().execute(() -> {
|
|
ccCpy.writeAndFlush(data.retainedDuplicate()).addListener(future ->
|
|
ccCpy.writeAndFlush(data2.retainedDuplicate()));
|
|
});
|
|
|
|
assertTrue(messageLatch.await(5, SECONDS));
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
} finally {
|
|
data.release();
|
|
data2.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testPeerWriteInWritePromiseCompleteDifferentEventLoopPreservesOrder() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch messageLatch = new CountDownLatch(2);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
final ByteBuf data2 = Unpooled.wrappedBuffer(new byte[512]);
|
|
final CountDownLatch serverChannelLatch = new CountDownLatch(1);
|
|
final AtomicReference<Channel> serverChannelRef = new AtomicReference<>();
|
|
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data2.equals(msg)) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data.equals(msg)) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
serverChannelRef.set(ch);
|
|
serverChannelLatch.countDown();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
assertTrue(serverChannelLatch.await(5, SECONDS));
|
|
|
|
final Channel ccCpy = cc;
|
|
// Make sure a write operation is executed in the eventloop
|
|
cc.pipeline().lastContext().executor().execute(() -> {
|
|
ccCpy.writeAndFlush(data.retainedDuplicate()).addListener(future -> {
|
|
Channel serverChannelCpy = serverChannelRef.get();
|
|
serverChannelCpy.writeAndFlush(data2.retainedDuplicate());
|
|
});
|
|
});
|
|
|
|
assertTrue(messageLatch.await(5, SECONDS));
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
data.release();
|
|
data2.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testPeerWriteInWritePromiseCompleteSameEventLoopPreservesOrder() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch messageLatch = new CountDownLatch(2);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
final ByteBuf data2 = Unpooled.wrappedBuffer(new byte[512]);
|
|
final CountDownLatch serverChannelLatch = new CountDownLatch(1);
|
|
final AtomicReference<Channel> serverChannelRef = new AtomicReference<>();
|
|
|
|
try {
|
|
cb.group(sharedGroup)
|
|
.channel(LocalChannel.class)
|
|
.handler(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data2.equals(msg) && messageLatch.getCount() == 1) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
|
|
sb.group(sharedGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data.equals(msg) && messageLatch.getCount() == 2) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
messageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
serverChannelRef.set(ch);
|
|
serverChannelLatch.countDown();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
assertTrue(serverChannelLatch.await(5, SECONDS));
|
|
|
|
final Channel ccCpy = cc;
|
|
// Make sure a write operation is executed in the eventloop
|
|
cc.pipeline().lastContext().executor().execute(() -> {
|
|
ccCpy.writeAndFlush(data.retainedDuplicate()).addListener(future -> {
|
|
Channel serverChannelCpy = serverChannelRef.get();
|
|
serverChannelCpy.writeAndFlush(
|
|
data2.retainedDuplicate());
|
|
});
|
|
});
|
|
|
|
assertTrue(messageLatch.await(5, SECONDS));
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
} finally {
|
|
data.release();
|
|
data2.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testWriteWhilePeerIsClosedReleaseObjectAndFailPromise() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
final CountDownLatch serverMessageLatch = new CountDownLatch(1);
|
|
final LatchChannelFutureListener serverChannelCloseLatch = new LatchChannelFutureListener(1);
|
|
final LatchChannelFutureListener clientChannelCloseLatch = new LatchChannelFutureListener(1);
|
|
final CountDownLatch writeFailLatch = new CountDownLatch(1);
|
|
final ByteBuf data = Unpooled.wrappedBuffer(new byte[1024]);
|
|
final ByteBuf data2 = Unpooled.wrappedBuffer(new byte[512]);
|
|
final CountDownLatch serverChannelLatch = new CountDownLatch(1);
|
|
final AtomicReference<Channel> serverChannelRef = new AtomicReference<>();
|
|
|
|
try {
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler());
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new ChannelHandler() {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
if (data.equals(msg)) {
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
serverMessageLatch.countDown();
|
|
} else {
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
}
|
|
});
|
|
serverChannelRef.set(ch);
|
|
serverChannelLatch.countDown();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = cb.connect(sc.localAddress()).get();
|
|
assertTrue(serverChannelLatch.await(5, SECONDS));
|
|
|
|
final Channel ccCpy = cc;
|
|
final Channel serverChannelCpy = serverChannelRef.get();
|
|
serverChannelCpy.closeFuture().addListener(serverChannelCloseLatch);
|
|
ccCpy.closeFuture().addListener(clientChannelCloseLatch);
|
|
|
|
// Make sure a write operation is executed in the eventloop
|
|
cc.pipeline().lastContext().executor().execute(() ->
|
|
ccCpy.writeAndFlush(data.retainedDuplicate())
|
|
.addListener(future -> {
|
|
serverChannelCpy.executor().execute(() -> {
|
|
// The point of this test is to write while the peer is closed, so we should
|
|
// ensure the peer is actually closed before we write.
|
|
int waitCount = 0;
|
|
while (ccCpy.isOpen()) {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException ignored) {
|
|
// ignored
|
|
}
|
|
if (++waitCount > 5) {
|
|
fail();
|
|
}
|
|
}
|
|
serverChannelCpy.writeAndFlush(data2.retainedDuplicate())
|
|
.addListener(future1 -> {
|
|
if (!future1.isSuccess() &&
|
|
future1.cause() instanceof ClosedChannelException) {
|
|
writeFailLatch.countDown();
|
|
}
|
|
});
|
|
});
|
|
ccCpy.close();
|
|
}));
|
|
|
|
assertTrue(serverMessageLatch.await(5, SECONDS));
|
|
assertTrue(writeFailLatch.await(5, SECONDS));
|
|
assertTrue(serverChannelCloseLatch.await(5, SECONDS));
|
|
assertTrue(clientChannelCloseLatch.await(5, SECONDS));
|
|
assertFalse(ccCpy.isOpen());
|
|
assertFalse(serverChannelCpy.isOpen());
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
} finally {
|
|
data.release();
|
|
data2.release();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
|
|
public void testConnectFutureBeforeChannelActive() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new ChannelHandler() { });
|
|
|
|
sb.group(group2)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new TestHandler());
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
cc = cb.register().get();
|
|
|
|
final AtomicReference<Future<Void>> ref = new AtomicReference<>();
|
|
final Promise<Void> assertPromise = cc.executor().newPromise();
|
|
|
|
cc.pipeline().addLast(new TestHandler() {
|
|
@Override
|
|
public Future<Void> connect(ChannelHandlerContext ctx,
|
|
SocketAddress remoteAddress, SocketAddress localAddress) {
|
|
Future<Void> future = super.connect(ctx, remoteAddress, localAddress);
|
|
ref.set(future);
|
|
return future;
|
|
}
|
|
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
// Ensure the promise was done before the handler method is triggered.
|
|
if (ref.get().isDone()) {
|
|
assertPromise.setSuccess(null);
|
|
} else {
|
|
assertPromise.setFailure(new AssertionError("connect promise should be done"));
|
|
}
|
|
}
|
|
});
|
|
// Connect to the server
|
|
cc.connect(sc.localAddress()).sync();
|
|
Future<Void> f = ref.get().sync();
|
|
|
|
assertPromise.syncUninterruptibly();
|
|
assertTrue(f.isSuccess());
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testConnectionRefused() throws Throwable {
|
|
try {
|
|
Bootstrap sb = new Bootstrap();
|
|
assertTrue(assertThrows(CompletionException.class, () -> sb.group(group1)
|
|
.channel(LocalChannel.class)
|
|
.handler(new TestHandler())
|
|
.connect(LocalAddress.ANY).syncUninterruptibly()).getCause() instanceof ConnectException);
|
|
} catch (CompletionException e) {
|
|
throw e.getCause();
|
|
}
|
|
}
|
|
|
|
private static final class LatchChannelFutureListener extends CountDownLatch implements FutureListener<Object> {
|
|
private LatchChannelFutureListener(int count) {
|
|
super(count);
|
|
}
|
|
|
|
@Override
|
|
public void operationComplete(Future<?> future) throws Exception {
|
|
countDown();
|
|
}
|
|
}
|
|
|
|
private static void closeChannel(Channel cc) {
|
|
if (cc != null) {
|
|
cc.close().syncUninterruptibly();
|
|
}
|
|
}
|
|
|
|
static class TestHandler implements ChannelHandler {
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
logger.info(String.format("Received message: %s", msg));
|
|
ReferenceCountUtil.safeRelease(msg);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testNotLeakBuffersWhenCloseByRemotePeer() throws Exception {
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(sharedGroup)
|
|
.channel(LocalChannel.class)
|
|
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
|
|
@Override
|
|
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
|
|
ctx.writeAndFlush(ctx.alloc().buffer().writeZero(100));
|
|
}
|
|
|
|
@Override
|
|
public void messageReceived(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
|
|
// Just drop the buffer
|
|
}
|
|
});
|
|
|
|
sb.group(sharedGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelInitializer<LocalChannel>() {
|
|
@Override
|
|
public void initChannel(LocalChannel ch) throws Exception {
|
|
ch.pipeline().addLast(new SimpleChannelInboundHandler<ByteBuf>() {
|
|
|
|
@Override
|
|
public void messageReceived(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
|
|
while (buffer.isReadable()) {
|
|
// Fill the ChannelOutboundBuffer with multiple buffers
|
|
ctx.write(buffer.readRetainedSlice(1));
|
|
}
|
|
// Flush and so transfer the written buffers to the inboundBuffer of the remote peer.
|
|
// After this point the remote peer is responsible to release all the buffers.
|
|
ctx.flush();
|
|
// This close call will trigger the remote peer close as well.
|
|
ctx.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
LocalChannel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
|
|
// Connect to the server
|
|
cc = (LocalChannel) cb.connect(sc.localAddress()).get();
|
|
|
|
// Close the channel
|
|
closeChannel(cc);
|
|
assertTrue(cc.inboundBuffer.isEmpty());
|
|
closeChannel(sc);
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
private static void writeAndFlushReadOnSuccess(final ChannelHandlerContext ctx, Object msg) {
|
|
ctx.writeAndFlush(msg).addListener(future -> {
|
|
if (future.isSuccess()) {
|
|
ctx.read();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testAutoReadDisabledSharedGroup() throws Exception {
|
|
testAutoReadDisabled(sharedGroup, sharedGroup);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testAutoReadDisabledDifferentGroup() throws Exception {
|
|
testAutoReadDisabled(group1, group2);
|
|
}
|
|
|
|
private static void testAutoReadDisabled(EventLoopGroup serverGroup, EventLoopGroup clientGroup) throws Exception {
|
|
final CountDownLatch latch = new CountDownLatch(100);
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(serverGroup)
|
|
.channel(LocalChannel.class)
|
|
.option(ChannelOption.AUTO_READ, false)
|
|
.handler(new ChannelHandler() {
|
|
|
|
@Override
|
|
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
|
|
writeAndFlushReadOnSuccess(ctx, "test");
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
writeAndFlushReadOnSuccess(ctx, msg);
|
|
}
|
|
});
|
|
|
|
sb.group(clientGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.childOption(ChannelOption.AUTO_READ, false)
|
|
.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
|
|
ctx.read();
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
latch.countDown();
|
|
if (latch.getCount() > 0) {
|
|
writeAndFlushReadOnSuccess(ctx, msg);
|
|
}
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
cc = cb.connect(TEST_ADDRESS).get();
|
|
|
|
latch.await();
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testMaxMessagesPerReadRespectedWithAutoReadSharedGroup() throws Exception {
|
|
testMaxMessagesPerReadRespected(sharedGroup, sharedGroup, true);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testMaxMessagesPerReadRespectedWithoutAutoReadSharedGroup() throws Exception {
|
|
testMaxMessagesPerReadRespected(sharedGroup, sharedGroup, false);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testMaxMessagesPerReadRespectedWithAutoReadDifferentGroup() throws Exception {
|
|
testMaxMessagesPerReadRespected(group1, group2, true);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testMaxMessagesPerReadRespectedWithoutAutoReadDifferentGroup() throws Exception {
|
|
testMaxMessagesPerReadRespected(group1, group2, false);
|
|
}
|
|
|
|
private static void testMaxMessagesPerReadRespected(
|
|
EventLoopGroup serverGroup, EventLoopGroup clientGroup, final boolean autoRead) throws Exception {
|
|
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(serverGroup)
|
|
.channel(LocalChannel.class)
|
|
.option(ChannelOption.AUTO_READ, autoRead)
|
|
.option(ChannelOption.MAX_MESSAGES_PER_READ, 1)
|
|
.handler(new ChannelReadHandler(countDownLatch, autoRead));
|
|
sb.group(clientGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.childHandler(new ChannelHandler() {
|
|
@Override
|
|
public void channelActive(final ChannelHandlerContext ctx) {
|
|
for (int i = 0; i < 10; i++) {
|
|
ctx.write(i);
|
|
}
|
|
ctx.flush();
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
cc = cb.connect(TEST_ADDRESS).get();
|
|
|
|
countDownLatch.await();
|
|
} finally {
|
|
closeChannel(cc);
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testServerMaxMessagesPerReadRespectedWithAutoReadSharedGroup() throws Exception {
|
|
testServerMaxMessagesPerReadRespected(sharedGroup, sharedGroup, true);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testServerMaxMessagesPerReadRespectedWithoutAutoReadSharedGroup() throws Exception {
|
|
testServerMaxMessagesPerReadRespected(sharedGroup, sharedGroup, false);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testServerMaxMessagesPerReadRespectedWithAutoReadDifferentGroup() throws Exception {
|
|
testServerMaxMessagesPerReadRespected(group1, group2, true);
|
|
}
|
|
|
|
@Test
|
|
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
|
public void testServerMaxMessagesPerReadRespectedWithoutAutoReadDifferentGroup() throws Exception {
|
|
testServerMaxMessagesPerReadRespected(group1, group2, false);
|
|
}
|
|
|
|
private static void testServerMaxMessagesPerReadRespected(
|
|
EventLoopGroup serverGroup, EventLoopGroup clientGroup, final boolean autoRead) throws Exception {
|
|
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
|
Bootstrap cb = new Bootstrap();
|
|
ServerBootstrap sb = new ServerBootstrap();
|
|
|
|
cb.group(serverGroup)
|
|
.channel(LocalChannel.class)
|
|
.handler(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) {
|
|
// NOOP
|
|
}
|
|
});
|
|
|
|
sb.group(clientGroup)
|
|
.channel(LocalServerChannel.class)
|
|
.option(ChannelOption.AUTO_READ, autoRead)
|
|
.option(ChannelOption.MAX_MESSAGES_PER_READ, 1)
|
|
.handler(new ChannelReadHandler(countDownLatch, autoRead))
|
|
.childHandler(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
protected void initChannel(Channel ch) {
|
|
// NOOP
|
|
}
|
|
});
|
|
|
|
Channel sc = null;
|
|
Channel cc = null;
|
|
try {
|
|
// Start server
|
|
sc = sb.bind(TEST_ADDRESS).get();
|
|
for (int i = 0; i < 5; i++) {
|
|
try {
|
|
cc = cb.connect(TEST_ADDRESS).get();
|
|
} finally {
|
|
closeChannel(cc);
|
|
}
|
|
}
|
|
|
|
countDownLatch.await();
|
|
} finally {
|
|
closeChannel(sc);
|
|
}
|
|
}
|
|
|
|
private static final class ChannelReadHandler implements ChannelHandler {
|
|
|
|
private final CountDownLatch latch;
|
|
private final boolean autoRead;
|
|
private int read;
|
|
|
|
ChannelReadHandler(CountDownLatch latch, boolean autoRead) {
|
|
this.latch = latch;
|
|
this.autoRead = autoRead;
|
|
}
|
|
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) {
|
|
if (!autoRead) {
|
|
ctx.read();
|
|
}
|
|
ctx.fireChannelActive();
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
|
|
assertEquals(0, read);
|
|
read++;
|
|
ctx.fireChannelRead(msg);
|
|
}
|
|
|
|
@Override
|
|
public void channelReadComplete(final ChannelHandlerContext ctx) {
|
|
assertEquals(1, read);
|
|
latch.countDown();
|
|
if (latch.getCount() > 0) {
|
|
if (!autoRead) {
|
|
// The read will be scheduled 100ms in the future to ensure we not receive any
|
|
// channelRead calls in the meantime.
|
|
ctx.executor().schedule(() -> {
|
|
read = 0;
|
|
ctx.read();
|
|
}, 100, TimeUnit.MILLISECONDS);
|
|
} else {
|
|
read = 0;
|
|
}
|
|
} else {
|
|
read = 0;
|
|
}
|
|
ctx.fireChannelReadComplete();
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
ctx.fireExceptionCaught(cause);
|
|
ctx.close();
|
|
}
|
|
}
|
|
}
|