2019-09-03 08:40:17 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2019 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;
|
|
|
|
|
|
|
|
import io.netty.bootstrap.Bootstrap;
|
|
|
|
import io.netty.channel.AdaptiveRecvByteBufAllocator;
|
|
|
|
import io.netty.channel.Channel;
|
|
|
|
import io.netty.channel.ChannelFuture;
|
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelOption;
|
|
|
|
import io.netty.channel.SimpleChannelInboundHandler;
|
|
|
|
import io.netty.channel.socket.DatagramPacket;
|
|
|
|
import io.netty.testsuite.transport.TestsuitePermutation;
|
|
|
|
import io.netty.testsuite.transport.socket.AbstractDatagramTest;
|
2019-11-23 21:12:24 +01:00
|
|
|
import org.junit.Assume;
|
|
|
|
import org.junit.BeforeClass;
|
2019-09-03 08:40:17 +02:00
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
import java.net.InetSocketAddress;
|
2019-09-06 13:54:29 +02:00
|
|
|
import java.net.SocketAddress;
|
2019-09-03 08:40:17 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
|
|
|
import static org.junit.Assert.assertArrayEquals;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
import static org.junit.Assert.fail;
|
|
|
|
|
|
|
|
public class EpollDatagramScatteringReadTest extends AbstractDatagramTest {
|
|
|
|
|
2019-11-23 21:12:24 +01:00
|
|
|
@BeforeClass
|
|
|
|
public static void assumeRecvmmsgSupported() {
|
|
|
|
Assume.assumeTrue(Native.IS_SUPPORTING_RECVMMSG);
|
|
|
|
}
|
|
|
|
|
2019-09-03 08:40:17 +02:00
|
|
|
@Override
|
|
|
|
protected List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
|
|
|
|
return EpollSocketTestPermutation.INSTANCE.epollOnlyDatagram(internetProtocolFamily());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testScatteringReadPartial() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringReadPartial(Bootstrap sb, Bootstrap cb) throws Throwable {
|
2019-09-06 20:58:38 +02:00
|
|
|
testScatteringRead(sb, cb, false, true);
|
2019-09-03 08:40:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testScatteringRead() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringRead(Bootstrap sb, Bootstrap cb) throws Throwable {
|
2019-09-06 20:58:38 +02:00
|
|
|
testScatteringRead(sb, cb, false, false);
|
2019-09-03 08:40:17 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 20:58:38 +02:00
|
|
|
@Test
|
|
|
|
public void testScatteringReadConnectedPartial() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringReadConnectedPartial(Bootstrap sb, Bootstrap cb) throws Throwable {
|
|
|
|
testScatteringRead(sb, cb, true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testScatteringConnectedRead() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringConnectedRead(Bootstrap sb, Bootstrap cb) throws Throwable {
|
|
|
|
testScatteringRead(sb, cb, true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void testScatteringRead(Bootstrap sb, Bootstrap cb, boolean connected, boolean partial) throws Throwable {
|
2019-09-03 08:40:17 +02:00
|
|
|
int packetSize = 512;
|
|
|
|
int numPackets = 4;
|
2019-09-06 20:58:38 +02:00
|
|
|
|
2019-09-03 08:40:17 +02:00
|
|
|
sb.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(
|
|
|
|
packetSize, packetSize * (partial ? numPackets / 2 : numPackets), 64 * 1024));
|
|
|
|
sb.option(EpollChannelOption.MAX_DATAGRAM_PAYLOAD_SIZE, packetSize);
|
|
|
|
|
|
|
|
Channel sc = null;
|
|
|
|
Channel cc = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
cb.handler(new SimpleChannelInboundHandler<Object>() {
|
|
|
|
@Override
|
2019-11-01 07:23:07 +01:00
|
|
|
public void messageReceived(ChannelHandlerContext ctx, Object msgs) throws Exception {
|
2019-09-03 08:40:17 +02:00
|
|
|
// Nothing will be sent.
|
|
|
|
}
|
|
|
|
});
|
2019-09-06 13:54:29 +02:00
|
|
|
cc = cb.bind(newSocketAddress()).sync().channel();
|
|
|
|
final SocketAddress ccAddress = cc.localAddress();
|
|
|
|
|
2019-09-03 08:40:17 +02:00
|
|
|
final AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>();
|
|
|
|
final byte[] bytes = new byte[packetSize];
|
|
|
|
ThreadLocalRandom.current().nextBytes(bytes);
|
|
|
|
|
|
|
|
final CountDownLatch latch = new CountDownLatch(numPackets);
|
|
|
|
sb.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
|
|
|
|
private int counter;
|
|
|
|
@Override
|
|
|
|
public void channelReadComplete(ChannelHandlerContext ctx) {
|
|
|
|
assertTrue(counter > 1);
|
|
|
|
counter = 0;
|
|
|
|
ctx.read();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-11-01 07:23:07 +01:00
|
|
|
protected void messageReceived(ChannelHandlerContext ctx, DatagramPacket msg) {
|
2019-09-06 13:54:29 +02:00
|
|
|
assertEquals(ccAddress, msg.sender());
|
|
|
|
|
2019-09-03 08:40:17 +02:00
|
|
|
assertEquals(bytes.length, msg.content().readableBytes());
|
|
|
|
byte[] receivedBytes = new byte[bytes.length];
|
|
|
|
msg.content().readBytes(receivedBytes);
|
|
|
|
assertArrayEquals(bytes, receivedBytes);
|
|
|
|
|
|
|
|
counter++;
|
|
|
|
latch.countDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
errorRef.compareAndSet(null, cause);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
sb.option(ChannelOption.AUTO_READ, false);
|
|
|
|
sc = sb.bind(newSocketAddress()).sync().channel();
|
|
|
|
|
2019-09-06 20:58:38 +02:00
|
|
|
if (connected) {
|
|
|
|
sc.connect(cc.localAddress()).syncUninterruptibly();
|
|
|
|
}
|
|
|
|
|
2019-09-03 08:40:17 +02:00
|
|
|
InetSocketAddress addr = (InetSocketAddress) sc.localAddress();
|
|
|
|
|
|
|
|
List<ChannelFuture> futures = new ArrayList<ChannelFuture>(numPackets);
|
|
|
|
for (int i = 0; i < numPackets; i++) {
|
|
|
|
futures.add(cc.write(new DatagramPacket(cc.alloc().directBuffer().writeBytes(bytes), addr)));
|
|
|
|
}
|
|
|
|
|
|
|
|
cc.flush();
|
|
|
|
|
|
|
|
for (ChannelFuture f: futures) {
|
|
|
|
f.sync();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enable autoread now which also triggers a read, this should cause scattering reads (recvmmsg) to happen.
|
|
|
|
sc.config().setAutoRead(true);
|
|
|
|
|
|
|
|
if (!latch.await(10, TimeUnit.SECONDS)) {
|
|
|
|
Throwable error = errorRef.get();
|
|
|
|
if (error != null) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
fail("Timeout while waiting for packets");
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (cc != null) {
|
|
|
|
cc.close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
if (sc != null) {
|
|
|
|
sc.close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-28 09:03:54 +01:00
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testScatteringReadWithSmallBuffer() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringReadWithSmallBuffer(Bootstrap sb, Bootstrap cb) throws Throwable {
|
|
|
|
testScatteringReadWithSmallBuffer0(sb, cb, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testScatteringConnectedReadWithSmallBuffer() throws Throwable {
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void testScatteringConnectedReadWithSmallBuffer(Bootstrap sb, Bootstrap cb) throws Throwable {
|
|
|
|
testScatteringReadWithSmallBuffer0(sb, cb, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void testScatteringReadWithSmallBuffer0(Bootstrap sb, Bootstrap cb, boolean connected) throws Throwable {
|
|
|
|
int packetSize = 16;
|
|
|
|
|
|
|
|
sb.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(1400, 1400, 64 * 1024));
|
|
|
|
sb.option(EpollChannelOption.MAX_DATAGRAM_PAYLOAD_SIZE, 1400);
|
|
|
|
|
|
|
|
Channel sc = null;
|
|
|
|
Channel cc = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
cb.handler(new SimpleChannelInboundHandler<Object>() {
|
|
|
|
@Override
|
2019-11-28 11:31:21 +01:00
|
|
|
public void messageReceived(ChannelHandlerContext ctx, Object msgs) {
|
2019-11-28 09:03:54 +01:00
|
|
|
// Nothing will be sent.
|
|
|
|
}
|
|
|
|
});
|
|
|
|
cc = cb.bind(newSocketAddress()).sync().channel();
|
|
|
|
final SocketAddress ccAddress = cc.localAddress();
|
|
|
|
|
|
|
|
final AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>();
|
|
|
|
final byte[] bytes = new byte[packetSize];
|
2019-11-28 11:31:21 +01:00
|
|
|
ThreadLocalRandom.current().nextBytes(bytes);
|
2019-11-28 09:03:54 +01:00
|
|
|
|
|
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
sb.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
|
|
|
|
|
|
|
|
@Override
|
2019-11-28 11:31:21 +01:00
|
|
|
protected void messageReceived(ChannelHandlerContext ctx, DatagramPacket msg) {
|
2019-11-28 09:03:54 +01:00
|
|
|
assertEquals(ccAddress, msg.sender());
|
|
|
|
|
|
|
|
assertEquals(bytes.length, msg.content().readableBytes());
|
|
|
|
byte[] receivedBytes = new byte[bytes.length];
|
|
|
|
msg.content().readBytes(receivedBytes);
|
|
|
|
assertArrayEquals(bytes, receivedBytes);
|
|
|
|
|
|
|
|
latch.countDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
errorRef.compareAndSet(null, cause);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
sc = sb.bind(newSocketAddress()).sync().channel();
|
|
|
|
|
|
|
|
if (connected) {
|
|
|
|
sc.connect(cc.localAddress()).syncUninterruptibly();
|
|
|
|
}
|
|
|
|
|
|
|
|
InetSocketAddress addr = (InetSocketAddress) sc.localAddress();
|
|
|
|
|
|
|
|
cc.writeAndFlush(new DatagramPacket(cc.alloc().directBuffer().writeBytes(bytes), addr)).sync();
|
|
|
|
|
|
|
|
if (!latch.await(10, TimeUnit.SECONDS)) {
|
|
|
|
Throwable error = errorRef.get();
|
|
|
|
if (error != null) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
fail("Timeout while waiting for packets");
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (cc != null) {
|
|
|
|
cc.close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
if (sc != null) {
|
|
|
|
sc.close().syncUninterruptibly();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-03 08:40:17 +02:00
|
|
|
}
|