/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltcore.messaging; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Selector; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import org.voltcore.utils.PortGenerator; import junit.framework.TestCase; public class TestMessaging extends TestCase { static final PortGenerator m_portGenerator = new PortGenerator(); public static class MsgTest extends VoltMessage { static byte[] globalValue; byte[] m_localValue; int m_length; static void initWithSize(int size) { Random r = new Random(); globalValue = new byte[size]; for (int i = 0; i < size; i++) globalValue[i] = (byte) r.nextInt(Byte.MAX_VALUE); } @Override public int getSerializedSize() { return super.getSerializedSize() + m_localValue.length; } @Override public void initFromBuffer(ByteBuffer buf) { m_length = buf.limit() - buf.position(); m_localValue = new byte[m_length]; buf.get(m_localValue); } @Override public void flattenToBuffer(ByteBuffer buf) { buf.put(MessageFactory.DUMMY_ID); buf.put(m_localValue); assert(buf.position() == buf.capacity()); } public void setValues() { m_localValue = new byte[globalValue.length]; for (int i = 0; i < globalValue.length; i++) m_localValue[i] = globalValue[i]; } public boolean verify() { if (globalValue.length != m_localValue.length) return false; for (int i = 0; i < globalValue.length; i++) { if (globalValue[i] != m_localValue[i]) { System.err.printf("MsgTst.verify() failed at byte: %d\n", i); return false; } } return true; } } public static class MessageFactory extends org.voltcore.messaging.VoltMessageFactory { final public static byte DUMMY_ID = VOLTCORE_MESSAGE_ID_MAX + 1; @Override protected VoltMessage instantiate_local(byte messageType) { // instantiate a new message instance according to the id VoltMessage message = null; switch (messageType) { case DUMMY_ID: message = new MsgTest(); break; } return message; } } private static List<HostMessenger.Config> getConfigs(int hostCount) { List<HostMessenger.Config> configs = HostMessenger.Config.generate(m_portGenerator, hostCount); for (HostMessenger.Config config: configs) { config.factory = new MessageFactory(); } return configs; } public void testSimple() throws Exception { List<HostMessenger.Config> configs = getConfigs(2); HostMessenger msg1 = new HostMessenger(configs.get(0), null); msg1.start(); HostMessenger msg2 = new HostMessenger(configs.get(1), null); msg2.start(); System.out.println("Waiting for socketjoiners..."); msg1.waitForGroupJoin(2); System.out.println("Finished socket joiner for msg1"); msg2.waitForGroupJoin(2); System.out.println("Finished socket joiner for msg2"); assertEquals(msg1.getHostId(), 0); assertEquals(msg2.getHostId(), 1); Mailbox mb1 = msg1.createMailbox(); Mailbox mb2 = msg2.createMailbox(); long siteId2 = mb2.getHSId(); MsgTest.initWithSize(16); MsgTest mt = new MsgTest(); mt.setValues(); mb1.send(siteId2, mt); MsgTest mt2 = null; while (mt2 == null) { mt2 = (MsgTest) mb2.recv(); } assertTrue(mt2.verify()); // Do it again MsgTest.initWithSize(32); mt = new MsgTest(); mt.setValues(); mb1.send(siteId2, mt); mt2 = null; while (mt2 == null) { mt2 = (MsgTest) mb2.recv(); } assertTrue(mt2.verify()); // Do it a final time with a message that should block on write. // spin on a complete network message here - maybe better to write // the above cases this way too? for (int i=0; i < 3; ++i) { MsgTest.initWithSize(4280034); mt = new MsgTest(); mt.setValues(); mb1.send(siteId2, mt); mt2 = null; while (mt2 == null) { mt2 = (MsgTest) mb2.recv(); } assertTrue(mt.verify()); } msg1.shutdown(); msg2.shutdown(); } public void testMultiMailbox() throws Exception { List<HostMessenger.Config> configs = getConfigs(3); HostMessenger msg1 = new HostMessenger(configs.get(0), null); msg1.start(); HostMessenger msg2 = new HostMessenger(configs.get(1), null); msg2.start(); HostMessenger msg3 = new HostMessenger(configs.get(2), null); msg3.start(); System.out.println("Waiting for socketjoiners..."); msg1.waitForGroupJoin(3); System.out.println("Finished socket joiner for msg1"); msg2.waitForGroupJoin(3); System.out.println("Finished socket joiner for msg2"); msg3.waitForGroupJoin(3); System.out.println("Finished socket joiner for msg3"); assertTrue(msg1.getHostId() != msg2.getHostId() && msg2.getHostId() != msg3.getHostId()); //assertTrue(msg2.getHostId() == 1); //assertTrue(msg3.getHostId() == 2); Mailbox mb1 = msg1.createMailbox(); Mailbox mb2 = msg2.createMailbox(); Mailbox mb3 = msg3.createMailbox(); Mailbox mb4 = msg3.createMailbox(); Mailbox mb5 = msg1.createMailbox(); long siteId5 = mb5.getHSId(); long siteId2 = mb2.getHSId(); long siteId3 = mb3.getHSId(); long siteId4 = mb4.getHSId(); MsgTest.initWithSize(16); MsgTest mt = new MsgTest(); mt.setValues(); int msgCount = 0; mb1.send(new long[] {siteId2,siteId3,siteId5,siteId4}, mt); long now = System.currentTimeMillis(); MsgTest mt2 = null, mt3 = null, mt4 = null, mt5 = null; // run (for no more than 5s) until all 4 messages have arrived // this code is really weird, but it is more accurate than just // running until you get 4 messages. It actually makes sure they // are the right messages. while (msgCount < 4) { assertTrue((System.currentTimeMillis() - now) < 5000); if (mt2 == null) { mt2 = (MsgTest) mb2.recv(); if (mt2 != null) { assertTrue(mt2.verify()); msgCount++; } } if (mt3 == null) { mt3 = (MsgTest) mb3.recv(); if (mt3 != null) { assertTrue(mt3.verify()); msgCount++; } } if (mt4 == null) { mt4 = (MsgTest) mb4.recv(); if (mt4 != null) { assertTrue(mt4.verify()); msgCount++; } } if (mt5 == null) { mt5 = (MsgTest) mb5.recv(); if (mt5 != null) { assertTrue(mt5.verify()); msgCount++; } } } mb3.send(new long[] {siteId5}, mt); assertEquals(((SiteMailbox)mb2).getWaitingCount(), 0); assertEquals(((SiteMailbox)mb3).getWaitingCount(), 0); assertEquals(((SiteMailbox)mb4).getWaitingCount(), 0); // check that there is a single message for mb5 // again, weird code, but I think it's right (jhugg) int wc = 0; now = System.currentTimeMillis(); while (wc != 1) { assertTrue((System.currentTimeMillis() - now) < 5000); wc = ((SiteMailbox)mb5).getWaitingCount(); if (wc == 0) assertTrue(wc < 2); } msg1.shutdown(); msg2.shutdown(); msg3.shutdown(); } class MockNewNode extends Thread { AtomicBoolean m_ready = new AtomicBoolean(false); final HostMessenger.Config config; MockNewNode(HostMessenger.Config config) { this.config = config; } void waitUntilReady() { while (!m_ready.get()) Thread.yield(); } @Override public void run() { try { HostMessenger msg = new HostMessenger(config, null); msg.start(); m_ready.set(true); msg.waitForGroupJoin(2); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } } public void testFailAndRejoin() throws Exception { /* Why is throwing away a selector interesting !? */ try { Selector.open(); } catch (IOException ex) { throw new RuntimeException(ex); } List<HostMessenger.Config> configs = getConfigs(2); HostMessenger msg1 = new HostMessenger(configs.get(0), null); msg1.start(); HostMessenger msg2 = new HostMessenger(configs.get(1), null); msg2.start(); System.out.println("Waiting for socketjoiners..."); msg1.waitForGroupJoin(2); System.out.println("Finished socket joiner for msg1"); msg2.waitForGroupJoin(2); System.out.println("Finished socket joiner for msg2"); // kill host #2 // triggers the fault manager msg2.closeForeignHostSocket(msg1.getHostId()); msg2.shutdown(); // this is just to wait for the fault manager to kick in Thread.sleep(50); // wait until the fault manager has kicked in for (int i = 0; msg1.countForeignHosts() > 0; i++) { if (i > 10) fail(); Thread.sleep(50); } assertEquals(0, msg1.countForeignHosts()); // rejoin the network in a new thread MockNewNode newnode = new MockNewNode(configs.get(1)); newnode.start(); newnode.waitUntilReady(); // this is just for extra safety Thread.sleep(50); // this timeout is rather lousy, but neither is it exception safe! newnode.join(1000); if (newnode.isAlive()) fail(); msg1.shutdown(); } }