2014-04-11 15:54:31 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2014 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.epoll;
|
|
|
|
|
2014-04-17 11:19:00 +02:00
|
|
|
import io.netty.bootstrap.AbstractBootstrap;
|
|
|
|
import io.netty.bootstrap.Bootstrap;
|
2014-04-11 15:54:31 +02:00
|
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
|
|
import io.netty.channel.ChannelFuture;
|
|
|
|
import io.netty.channel.ChannelHandler;
|
|
|
|
import io.netty.channel.ChannelHandlerAdapter;
|
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
|
|
import io.netty.testsuite.util.TestUtils;
|
2014-04-17 11:19:00 +02:00
|
|
|
import io.netty.util.NetUtil;
|
|
|
|
import io.netty.util.ReferenceCountUtil;
|
|
|
|
import io.netty.util.ResourceLeakDetector;
|
2014-06-27 17:55:55 +02:00
|
|
|
import io.netty.util.internal.StringUtil;
|
2014-04-11 15:54:31 +02:00
|
|
|
import org.junit.Assert;
|
|
|
|
import org.junit.Assume;
|
2015-08-15 02:05:13 +02:00
|
|
|
import org.junit.Ignore;
|
2014-04-11 15:54:31 +02:00
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2014-04-17 11:19:00 +02:00
|
|
|
import java.net.DatagramPacket;
|
|
|
|
import java.net.DatagramSocket;
|
2014-04-11 15:54:31 +02:00
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.Socket;
|
2014-04-17 11:19:00 +02:00
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
import java.util.concurrent.Executors;
|
2014-04-11 15:54:31 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
|
|
|
public class EpollReuseAddrTest {
|
|
|
|
private static final int MAJOR;
|
|
|
|
private static final int MINOR;
|
|
|
|
private static final int BUGFIX;
|
|
|
|
static {
|
2016-01-30 20:47:12 +01:00
|
|
|
String kernelVersion = Native.KERNEL_VERSION;
|
2014-06-27 17:55:55 +02:00
|
|
|
int index = kernelVersion.indexOf('-');
|
2014-04-11 15:54:31 +02:00
|
|
|
if (index > -1) {
|
|
|
|
kernelVersion = kernelVersion.substring(0, index);
|
|
|
|
}
|
2014-06-27 17:55:55 +02:00
|
|
|
String[] versionParts = StringUtil.split(kernelVersion, '.');
|
|
|
|
if (versionParts.length <= 3) {
|
2014-04-11 15:54:31 +02:00
|
|
|
MAJOR = Integer.parseInt(versionParts[0]);
|
|
|
|
MINOR = Integer.parseInt(versionParts[1]);
|
2014-06-27 17:55:55 +02:00
|
|
|
if (versionParts.length == 3) {
|
|
|
|
BUGFIX = Integer.parseInt(versionParts[2]);
|
|
|
|
} else {
|
|
|
|
BUGFIX = 0;
|
|
|
|
}
|
2014-04-11 15:54:31 +02:00
|
|
|
} else {
|
2014-06-27 17:55:55 +02:00
|
|
|
throw new IllegalStateException("Can not parse kernel version " + kernelVersion);
|
2014-04-11 15:54:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2014-04-17 11:19:00 +02:00
|
|
|
public void testMultipleBindSocketChannelWithoutReusePortFails() {
|
2014-04-11 15:54:31 +02:00
|
|
|
Assume.assumeTrue(versionEqOrGt(3, 9, 0));
|
2014-04-17 11:19:00 +02:00
|
|
|
testMultipleBindDatagramChannelWithoutReusePortFails0(createServerBootstrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testMultipleBindDatagramChannelWithoutReusePortFails() {
|
|
|
|
Assume.assumeTrue(versionEqOrGt(3, 9, 0));
|
|
|
|
testMultipleBindDatagramChannelWithoutReusePortFails0(createBootstrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testMultipleBindDatagramChannelWithoutReusePortFails0(AbstractBootstrap<?, ?> bootstrap) {
|
|
|
|
bootstrap.handler(new DummyHandler());
|
2014-04-11 15:54:31 +02:00
|
|
|
ChannelFuture future = bootstrap.bind().syncUninterruptibly();
|
|
|
|
try {
|
|
|
|
bootstrap.bind().syncUninterruptibly();
|
|
|
|
Assert.fail();
|
|
|
|
} catch (Exception e) {
|
|
|
|
Assert.assertTrue(e instanceof IOException);
|
|
|
|
}
|
|
|
|
future.channel().close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test(timeout = 10000)
|
2014-04-17 11:19:00 +02:00
|
|
|
public void testMultipleBindSocketChannel() throws Exception {
|
2014-04-11 15:54:31 +02:00
|
|
|
Assume.assumeTrue(versionEqOrGt(3, 9, 0));
|
2014-04-17 11:19:00 +02:00
|
|
|
ServerBootstrap bootstrap = createServerBootstrap();
|
2014-04-11 15:54:31 +02:00
|
|
|
bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);
|
|
|
|
final AtomicBoolean accepted1 = new AtomicBoolean();
|
2014-04-17 11:19:00 +02:00
|
|
|
bootstrap.childHandler(new ServerSocketTestHandler(accepted1));
|
2014-04-11 15:54:31 +02:00
|
|
|
ChannelFuture future = bootstrap.bind().syncUninterruptibly();
|
2014-04-17 11:19:00 +02:00
|
|
|
InetSocketAddress address1 = (InetSocketAddress) future.channel().localAddress();
|
2014-04-11 15:54:31 +02:00
|
|
|
|
|
|
|
final AtomicBoolean accepted2 = new AtomicBoolean();
|
2014-04-17 11:19:00 +02:00
|
|
|
bootstrap.childHandler(new ServerSocketTestHandler(accepted2));
|
2014-04-11 15:54:31 +02:00
|
|
|
ChannelFuture future2 = bootstrap.bind().syncUninterruptibly();
|
2014-04-17 11:19:00 +02:00
|
|
|
InetSocketAddress address2 = (InetSocketAddress) future2.channel().localAddress();
|
2014-04-11 15:54:31 +02:00
|
|
|
|
2014-04-17 11:19:00 +02:00
|
|
|
Assert.assertEquals(address1, address2);
|
2014-04-11 15:54:31 +02:00
|
|
|
while (!accepted1.get() || !accepted2.get()) {
|
2014-04-17 11:19:00 +02:00
|
|
|
Socket socket = new Socket(address1.getAddress(), address1.getPort());
|
2014-04-11 15:54:31 +02:00
|
|
|
socket.setReuseAddress(true);
|
|
|
|
socket.close();
|
|
|
|
}
|
|
|
|
future.channel().close().syncUninterruptibly();
|
|
|
|
future2.channel().close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
|
2014-04-17 11:19:00 +02:00
|
|
|
@Test(timeout = 10000)
|
2015-08-15 02:05:13 +02:00
|
|
|
@Ignore // TODO: Unignore after making it pass on centos6-1 and debian7-1
|
2014-04-17 11:19:00 +02:00
|
|
|
public void testMultipleBindDatagramChannel() throws Exception {
|
|
|
|
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
|
|
|
|
Assume.assumeTrue(versionEqOrGt(3, 9, 0));
|
|
|
|
Bootstrap bootstrap = createBootstrap();
|
|
|
|
bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);
|
|
|
|
final AtomicBoolean received1 = new AtomicBoolean();
|
|
|
|
bootstrap.handler(new DatagramSocketTestHandler(received1));
|
|
|
|
ChannelFuture future = bootstrap.bind().syncUninterruptibly();
|
|
|
|
final InetSocketAddress address1 = (InetSocketAddress) future.channel().localAddress();
|
|
|
|
|
|
|
|
final AtomicBoolean received2 = new AtomicBoolean();
|
|
|
|
bootstrap.handler(new DatagramSocketTestHandler(received2));
|
|
|
|
ChannelFuture future2 = bootstrap.bind().syncUninterruptibly();
|
|
|
|
final InetSocketAddress address2 = (InetSocketAddress) future2.channel().localAddress();
|
|
|
|
|
|
|
|
Assert.assertEquals(address1, address2);
|
|
|
|
final byte[] bytes = "data".getBytes();
|
|
|
|
|
|
|
|
// fire up 16 Threads and send DatagramPackets to make sure we stress it enough to see DatagramPackets received
|
|
|
|
// on both sockets.
|
|
|
|
int count = 16;
|
|
|
|
final CountDownLatch latch = new CountDownLatch(count);
|
|
|
|
Runnable r = new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
DatagramSocket socket = new DatagramSocket();
|
|
|
|
while (!received1.get() || !received2.get()) {
|
|
|
|
socket.send(new DatagramPacket(
|
|
|
|
bytes, 0, bytes.length, address1.getAddress(), address1.getPort()));
|
|
|
|
}
|
|
|
|
socket.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
latch.countDown();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ExecutorService executor = Executors.newFixedThreadPool(count);
|
|
|
|
for (int i = 0 ; i < count; i++) {
|
|
|
|
executor.execute(r);
|
|
|
|
}
|
|
|
|
latch.await();
|
|
|
|
executor.shutdown();
|
|
|
|
future.channel().close().syncUninterruptibly();
|
|
|
|
future2.channel().close().syncUninterruptibly();
|
|
|
|
Assert.assertTrue(received1.get());
|
|
|
|
Assert.assertTrue(received2.get());
|
|
|
|
}
|
|
|
|
|
2014-06-27 17:55:55 +02:00
|
|
|
private static ServerBootstrap createServerBootstrap() {
|
2014-04-11 15:54:31 +02:00
|
|
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
|
|
|
bootstrap.group(EpollSocketTestPermutation.EPOLL_BOSS_GROUP, EpollSocketTestPermutation.EPOLL_WORKER_GROUP);
|
|
|
|
bootstrap.channel(EpollServerSocketChannel.class);
|
2014-04-17 11:19:00 +02:00
|
|
|
bootstrap.childHandler(new DummyHandler());
|
|
|
|
InetSocketAddress address = new InetSocketAddress(NetUtil.LOCALHOST, TestUtils.getFreePort());
|
|
|
|
bootstrap.localAddress(address);
|
|
|
|
return bootstrap;
|
|
|
|
}
|
|
|
|
|
2014-06-27 17:55:55 +02:00
|
|
|
private static Bootstrap createBootstrap() {
|
2014-04-17 11:19:00 +02:00
|
|
|
Bootstrap bootstrap = new Bootstrap();
|
|
|
|
bootstrap.group(EpollSocketTestPermutation.EPOLL_WORKER_GROUP);
|
|
|
|
bootstrap.channel(EpollDatagramChannel.class);
|
|
|
|
InetSocketAddress address = new InetSocketAddress(NetUtil.LOCALHOST, TestUtils.getFreePort());
|
2014-04-11 15:54:31 +02:00
|
|
|
bootstrap.localAddress(address);
|
|
|
|
return bootstrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean versionEqOrGt(int major, int minor, int bugfix) {
|
|
|
|
if (MAJOR > major) {
|
|
|
|
return true;
|
2014-06-27 17:55:55 +02:00
|
|
|
}
|
|
|
|
if (MAJOR == major) {
|
2014-04-11 15:54:31 +02:00
|
|
|
if (MINOR > minor) {
|
|
|
|
return true;
|
|
|
|
} else if (MINOR == minor) {
|
|
|
|
if (BUGFIX >= bugfix) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ChannelHandler.Sharable
|
2014-04-17 11:19:00 +02:00
|
|
|
private static class ServerSocketTestHandler extends ChannelInboundHandlerAdapter {
|
2014-04-11 15:54:31 +02:00
|
|
|
private final AtomicBoolean accepted;
|
|
|
|
|
2014-04-17 11:19:00 +02:00
|
|
|
ServerSocketTestHandler(AtomicBoolean accepted) {
|
2014-04-11 15:54:31 +02:00
|
|
|
this.accepted = accepted;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
|
|
accepted.set(true);
|
|
|
|
ctx.close();
|
|
|
|
}
|
|
|
|
}
|
2014-04-17 11:19:00 +02:00
|
|
|
|
|
|
|
@ChannelHandler.Sharable
|
|
|
|
private static class DatagramSocketTestHandler extends ChannelInboundHandlerAdapter {
|
|
|
|
private final AtomicBoolean received;
|
|
|
|
|
|
|
|
DatagramSocketTestHandler(AtomicBoolean received) {
|
|
|
|
this.received = received;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
|
|
ReferenceCountUtil.release(msg);
|
|
|
|
received.set(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ChannelHandler.Sharable
|
|
|
|
private static final class DummyHandler extends ChannelHandlerAdapter { }
|
2014-04-11 15:54:31 +02:00
|
|
|
}
|