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.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; /** * Tests multicast and unicast ordering of <em>regular</em> messages.<br/> * Regular messages from different senders can be delivered in parallel; messages from the same sender must be * delivered in send order.<br/> * There is no relation between multicast and unicast messages from a sender P; they are unrelated and therefore * delivered in parallel as well. * @author Bela Ban */ @Test(groups=Global.FUNCTIONAL,singleThreaded=true) public class OrderingTest { protected static final int NUM_MSGS=100000; protected static final int PRINT=NUM_MSGS / 5; protected static final int NUM_SENDERS=2; protected JChannel[] channels=new JChannel[NUM_SENDERS]; @BeforeMethod void init() throws Exception { for(int i=0; i < channels.length; i++) { channels[i]=createChannel(i).connect("OrderingTest.testFIFOOrder"); channels[i].setReceiver(new MyReceiver(channels[i].name())); } Util.waitUntilAllChannelsHaveSameView(10000, 500, channels); for(JChannel ch: channels) { SHUFFLE shuffle=new SHUFFLE(); ch.getProtocolStack().insertProtocol(shuffle, ProtocolStack.Position.ABOVE, Discovery.class); shuffle.init(); } } @AfterMethod void destroy() { Stream.of(channels).forEach(ch -> ch.getProtocolStack().removeProtocol(SHUFFLE.class)); Util.close(channels); } protected static JChannel createChannel(int index) throws Exception { return new JChannel(new SHARED_LOOPBACK(), new SHARED_LOOPBACK_PING(), // new SHUFFLE(), // reorders messages and message batches new NAKACK2().setValue("use_mcast_xmit", false).setValue("discard_delivered_msgs", true), new UNICAST3(), new STABLE().setValue("max_bytes", 50000).setValue("desired_avg_gossip", 1000), new GMS().setValue("print_local_addr", false), new UFC().setValue("max_credits", 2000000), new MFC().setValue("max_credits", 2000000), new FRAG2()) .name(String.valueOf((char)('A' +index))); } public void testMulticastFIFOOrdering() throws Exception { System.out.println("\n-- sending " + NUM_MSGS + " messages"); final CountDownLatch latch=new CountDownLatch(1); MySender[] senders=new MySender[NUM_SENDERS]; for(int i=0; i < senders.length; i++) { senders[i]=new MySender(channels[i], null, latch); senders[i].start(); } latch.countDown(); for(MySender sender: senders) sender.join(); System.out.println("-- senders done"); checkOrder(NUM_MSGS * NUM_SENDERS); } public void testUnicastFIFOOrdering() throws Exception { System.out.printf("\n-- sending %d unicast messages\n", NUM_MSGS); final CountDownLatch latch=new CountDownLatch(1); MySender[] senders=new MySender[NUM_SENDERS]; for(int i=0; i < senders.length; i++) { Address dest=channels[(i+1) % channels.length].getAddress(); senders[i]=new MySender(channels[i], dest, latch); System.out.printf("-- %s sends to %s\n", channels[i].getAddress(), dest); senders[i].start(); } latch.countDown(); for(MySender sender: senders) sender.join(); System.out.println("-- senders done"); checkOrder(NUM_MSGS); } protected void checkOrder(int expected_msgs) { System.out.println("\n-- waiting for message reception by all receivers:"); for(int i=0; i < 50; i++) { boolean done=true; for(JChannel ch: channels) { MyReceiver receiver=(MyReceiver)ch.getReceiver(); int received=receiver.getReceived(); if(received != expected_msgs) { done=false; break; } } if(done) break; Util.sleep(1000); } Stream.of(channels).forEach(ch -> System.out.printf("%s: %d\n", ch.getAddress(), ((MyReceiver)ch.getReceiver()).getReceived())); for(JChannel ch: channels) { MyReceiver receiver=(MyReceiver)ch.getReceiver(); assert receiver.getReceived() == expected_msgs : String.format("%s had %d messages (expected=%d)", receiver.name, receiver.getReceived(), expected_msgs); } System.out.println("\n-- checking message order"); for(JChannel ch: channels) { MyReceiver receiver=(MyReceiver)ch.getReceiver(); System.out.print(ch.getAddress() + ": "); boolean ok=receiver.getNumberOfErrors() == 0; System.out.println(ok? "OK" : "FAIL (" + receiver.getNumberOfErrors() + " errors)"); assert ok : receiver.getNumberOfErrors() + " errors"; } } protected static class MySender extends Thread { protected final JChannel ch; protected final Address dest; protected final CountDownLatch latch; public MySender(JChannel ch, Address dest, CountDownLatch latch) { this.ch=ch; this.dest=dest; this.latch=latch; } public void run() { try { latch.await(); } catch(InterruptedException e) { e.printStackTrace(); } for(int i=1; i <= NUM_MSGS; i++) { try { Message msg=new Message(dest, i); ch.send(msg); if(i % PRINT == 0) System.out.println(ch.getAddress() + ": " + i + " sent"); } catch(Exception e) { e.printStackTrace(); } } } } protected static class MyReceiver extends ReceiverAdapter { protected final ConcurrentMap<Address,Integer> map=new ConcurrentHashMap<>(); final AtomicInteger received=new AtomicInteger(0); protected int num_errors; protected final String name; public MyReceiver(String name) { this.name=name; } public int getNumberOfErrors() { return num_errors; } public int getReceived() { return received.intValue(); } public void receive(Message msg) { Integer num=msg.getObject(); Address sender=msg.getSrc(); Integer current_seqno=map.get(sender); if(current_seqno == null) { current_seqno=1; Integer tmp=map.putIfAbsent(sender, current_seqno); if(tmp != null) current_seqno=tmp; } if(current_seqno.intValue() == num) map.put(sender, current_seqno + 1); else num_errors++; if(received.incrementAndGet() % PRINT == 0) System.out.printf("%s: received %d\n", name, received.get()); } } }