package org.jgroups.tests;
import org.jgroups.*;
import org.jgroups.protocols.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Tests the reliable FIFO (NAKACK) protocol
* <p/>
* Two sender peers send 1000 messages to the group, where each message contains
* a long value, mirroring seqnos used. A receiver peer receives the messages
* from each sender and checks that seqnos are received in the correct order.
* <p/>
* An object all_msgs_recd is used to allow the main test thread to discover when
* all sent messages have been received.
* <p/>
* The test case passes if the expected number of messages is received, and messages
* are received in order from each sender. This implies that:
* (i) all messages from each peer were received (reliable) and
* (ii) all messages from each peer are received in order (FIFO)
* @author Richard Achmatowicz
* @author Bela Ban
*/
@Test(groups=Global.FUNCTIONAL, sequential=true)
public class NakackTest {
final static int NUM_PEERS=3;
final static int NUM_MSGS=1000;
final static int WAIT_TIMEOUT=10; // secs
final static int MSGS_PER_STATUS_LINE=100;
// convey assertion failure from thread to main framework
static boolean notFIFO=false;
static boolean allMsgsReceived=false;
JChannel[] channels=new JChannel[NUM_PEERS];
Thread[] threads=new Thread[NUM_PEERS];
//define senders and receivers
boolean[] isSender={false,true,true};
// used to wait for signal that all messages received
static final Object all_msgs_recd=new Object();
/**
* Set up a number of simulator instances wrapping NAKACK
*/
@BeforeMethod
void setUp() throws Exception {
// create new simulator instances
for(int i=0; i < NUM_PEERS; i++) {
channels[i]=createChannel();
channels[i].setName(Character.toString((char)(i + 'A')));
channels[i].connect("NakackTest");
}
// set up the receiver callbacks for each simulator
Receiver[] receivers=new Receiver[NUM_PEERS];
// set up the sender and the receiver callbacks, according to whether the peer is a sender or a receiver
for(int i=0; i < NUM_PEERS; i++) {
if(isSender[i])
receivers[i]=new ReceiverPeer(channels[i]);
else
receivers[i]=new ReceiverPeer(channels[i]);
channels[i].setReceiver(receivers[i]);
}
}
@AfterMethod
void tearDown() throws Exception {
for(int i=0; i < NUM_PEERS; i++)
channels[i].close();
}
/**
* Test to see thyat NAKACK delivery is reliable and FIFO.
*/
public void testReceptionOfAllMessages() throws TimeoutException {
Util.waitUntilAllChannelsHaveSameSize(10000, 1000, channels);
// start the NAKACK peers and let them exchange messages
for(int i=0; i < NUM_PEERS; i++) {
threads[i]=new MyNAKACKPeer(channels[i], isSender[i]);
threads[i].start();
}
// wait for the receiver peer to signal that it has received messages, or timeout
synchronized(all_msgs_recd) {
try {
all_msgs_recd.wait(WAIT_TIMEOUT * 1000);
}
catch(InterruptedException e) {
System.out.println("main thread interrupted");
}
}
// wait for the threads to terminate
try {
for(int i=0; i < NUM_PEERS; i++)
threads[i].join();
}
catch(InterruptedException e) {
}
// the test fails if:
// - a seqno is received out of order (not FIFO), or
// - not all messages are received in time allotted (allMsgsReceived)
Assert.assertTrue(allMsgsReceived, "Incorrect number of messages received by the receiver thread");
Assert.assertFalse(notFIFO, "Sequenece numbers for a peer not in correct order");
}
protected static JChannel createChannel() throws Exception {
JChannel ch=new JChannel(false);
ProtocolStack stack=new ProtocolStack();
ch.setProtocolStack(stack);
stack.addProtocol(new SHARED_LOOPBACK())
.addProtocol(new PING().setValue("timeout", 2000).setValue("num_initial_members", 3))
.addProtocol(new MERGE2().setValue("min_interval", 1000).setValue("max_interval", 3000))
.addProtocol(new NAKACK2().setValue("use_mcast_xmit", false))
.addProtocol(new UNICAST2())
.addProtocol(new STABLE().setValue("max_bytes", 50000))
.addProtocol(new GMS().setValue("print_local_addr", false))
.addProtocol(new UFC())
.addProtocol(new MFC())
.addProtocol(new FRAG2());
stack.init();
return ch;
}
/**
* This method should do the following:
* - receive messages from senders
* - check that sequence numbers for each sender are in order (with no gaps)
* - terminate when correct number of messages have been received
*/
static class ReceiverPeer extends ReceiverAdapter {
final JChannel channel;
int num_mgs_received=0;
ConcurrentMap<Address, Long> senders=new ConcurrentHashMap<Address, Long>();
public ReceiverPeer(JChannel channel) {
this.channel=channel;
}
/**
* Receive() is concurrent for different senders, but sequential per sender
* @param msg
*/
public void receive(Message msg) {
// keep track of seqno ordering of messages received
Address sender=msg.getSrc();
// get the expected next seqno for this sender
Long num=senders.get(sender);
if(num == null) {
num=new Long(1);
senders.putIfAbsent(sender, num);
}
long last_seqno=num.longValue();
try {
num=(Long)msg.getObject();
long received_seqno=num.longValue();
num_mgs_received++;
// 1. check if sequence numbers are in sequence
if(received_seqno == last_seqno) { // correct - update with next expected seqno
senders.put(sender, new Long(last_seqno + 1));
}
else {
// error, terminate test
notFIFO=true;
Assert.fail("FAIL: received msg #" + received_seqno + ", expected " + last_seqno);
}
Address address=channel.getAddress();
if(received_seqno % MSGS_PER_STATUS_LINE == 0 && received_seqno > 0)
System.out.println("<" + address + ">:" + "PASS: received msg #" + received_seqno + " from " + sender);
// condition to terminate the test - all messages received (whether in correct order or not)
if(num_mgs_received >= NakackTest.NUM_MSGS * (NUM_PEERS - 1)) {
// signal that all messages have been received - this will allow the receiver
// thread to terminate normally
synchronized(all_msgs_recd) {
// indicate that we have received the required number of messages
// to differentiate between timeout and notifyAll cases on monitor
allMsgsReceived=true;
all_msgs_recd.notifyAll();
}
}
}
catch(Exception ex) {
System.err.println(ex.toString());
}
}
public int getNumberOfReceivedMessages() {
return num_mgs_received;
}
}
static class MyNAKACKPeer extends Thread {
JChannel ch=null;
boolean sender=false;
public MyNAKACKPeer(JChannel ch, boolean sender) {
this.ch=ch;
this.sender=sender;
}
public void run() {
// senders send NUM_MSGS messages to all peers, beginning with seqno 1
if(sender) {
Address address=ch.getAddress();
for(int i=1; i <= NUM_MSGS; i++) {
try {
Message msg=new Message(null, address, new Long(i));
ch.send(msg);
if(i % MSGS_PER_STATUS_LINE == 0) // status indicator
System.out.println("<" + address + ">:" + " ==> " + i);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
}
}
}