/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.nio.tcp;
import com.hazelcast.internal.networking.ChannelWriter;
import com.hazelcast.internal.networking.ChannelReader;
import com.hazelcast.nio.Packet;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.TestThread;
import org.junit.Before;
import org.junit.Test;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
/**
* This test will concurrently write to a single connection and check if all the data transmitted, is received
* on the other side.
* <p/>
* In the past we had some issues with packet not getting written. So this test will write various size packets (from small
* to very large).
*/
public abstract class TcpIpConnection_TransferStressBaseTest extends TcpIpConnection_AbstractTest {
// total running time for writer threads
private static final long WRITER_THREAD_RUNNING_TIME_IN_SECONDS = TimeUnit.MINUTES.toSeconds(2);
// maximum number of pending packets
private static final int maxPendingPacketCount = 10000;
// we create the payloads up front and select randomly from them. This is the number of payloads we are creating
private static final int payloadCount = 10000;
private final AtomicBoolean stop = new AtomicBoolean(false);
private DummyPayload[] payloads;
@Before
public void setup() throws Exception {
super.setup();
startAllConnectionManagers();
}
@Test
public void testTinyPackets() throws Exception {
makePayloads(10);
testPackets();
}
@Test
public void testSmallPackets() throws Exception {
makePayloads(100);
testPackets();
}
@Test
public void testMediumPackets() throws Exception {
makePayloads(1000);
testPackets();
}
@Test(timeout = 10 * 60 * 1000)
public void testLargePackets() throws Exception {
makePayloads(10000);
testPackets((10 * 60 * 1000) - (WRITER_THREAD_RUNNING_TIME_IN_SECONDS * 1000));
}
@Test
public void testSemiRealisticPackets() throws Exception {
makeSemiRealisticPayloads();
testPackets();
}
private void testPackets() throws Exception {
testPackets(ASSERT_TRUE_EVENTUALLY_TIMEOUT);
}
private void testPackets(long verifyTimeoutInMillis) throws Exception {
TcpIpConnection c = connect(connManagerA, addressB);
WriteThread thread1 = new WriteThread(1, c);
WriteThread thread2 = new WriteThread(2, c);
logger.info("Starting");
thread1.start();
thread2.start();
sleepAndStop(stop, WRITER_THREAD_RUNNING_TIME_IN_SECONDS);
logger.info("Done");
thread1.assertSucceedsEventually();
thread2.assertSucceedsEventually();
// there is always one packet extra for the bind-request
final long expectedNormalPackets = thread1.normalPackets + thread2.normalPackets + 1;
final long expectedUrgentPackets = thread1.urgentPackets + thread2.urgentPackets;
logger.info("expected normal packets: " + expectedNormalPackets);
logger.info("expected priority packets: " + expectedUrgentPackets);
final ChannelWriter writer = c.getChannelWriter();
final ChannelReader reader = ((TcpIpConnection) connManagerB.getConnection(addressA)).getChannelReader();
long start = System.currentTimeMillis();
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
logger.info("writer total frames pending : " + writer.totalFramesPending());
logger.info("writer last write time millis : " + writer.lastWriteTimeMillis());
logger.info("reader total frames handled : " + reader.getNormalFramesReadCounter().get()
+ reader.getPriorityFramesReadCounter().get());
logger.info("reader last read time millis : " + reader.lastReadTimeMillis());
assertEquals(expectedNormalPackets, reader.getNormalFramesReadCounter().get());
assertEquals(expectedUrgentPackets, reader.getPriorityFramesReadCounter().get());
}
}, verifyTimeoutInMillis);
logger.info("Waiting for pending packets to be sent and received finished in "
+ (System.currentTimeMillis() - start) + " milliseconds");
}
private void makePayloads(int maxSize) {
Random random = new Random();
payloads = new DummyPayload[payloadCount];
for (int k = 0; k < payloads.length; k++) {
final byte[] bytes = new byte[random.nextInt(maxSize)];
payloads[k] = new DummyPayload(bytes, false);
}
}
private void makeSemiRealisticPayloads() {
Random random = new Random();
payloads = new DummyPayload[payloadCount];
for (int k = 0; k < payloads.length; k++) {
// one in every 100 packet we make big
boolean largePacket = random.nextInt(100) == 1;
boolean ultraLarge = false;
if (largePacket) {
ultraLarge = random.nextInt(10) == 1;
}
int byteCount;
if (ultraLarge) {
byteCount = random.nextInt(100000);
} else if (largePacket) {
byteCount = random.nextInt(10000);
} else {
byteCount = random.nextInt(300);
}
// one in every 100 packet we make urgent
boolean urgentPacket = random.nextInt(100) == 1;
payloads[k] = new DummyPayload(new byte[byteCount], urgentPacket);
}
}
public class WriteThread extends TestThread {
private final Random random = new Random();
private final ChannelWriter writeHandler;
private long normalPackets;
private long urgentPackets;
public WriteThread(int id, TcpIpConnection c) {
super("WriteThread-" + id);
this.writeHandler = c.getChannelWriter();
}
@Override
public void doRun() throws Throwable {
long prev = System.currentTimeMillis();
while (!stop.get()) {
Packet packet = nextPacket();
if (packet.isUrgent()) {
urgentPackets++;
} else {
normalPackets++;
}
writeHandler.write(packet);
long now = System.currentTimeMillis();
if (now > prev + 2000) {
prev = now;
logger.info("At normal-packets:" + normalPackets + " priority-packets:" + urgentPackets);
}
double usage = getUsage();
if (usage < 90) {
continue;
}
for (; ; ) {
sleep(random.nextInt(5));
if (getUsage() < 10 || stop.get()) {
break;
}
}
}
logger.info("Finished, normal packets written: " + normalPackets
+ " urgent packets written:" + urgentPackets
+ " total frames pending:" + writeHandler.totalFramesPending());
}
private double getUsage() {
return 100d * writeHandler.totalFramesPending() / maxPendingPacketCount;
}
public Packet nextPacket() {
DummyPayload payload = payloads[random.nextInt(payloads.length)];
Packet packet = new Packet(serializationService.toBytes(payload));
if (payload.isUrgent()) {
packet.raiseFlags(Packet.FLAG_URGENT);
}
return packet;
}
}
}