/* 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.zk; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.zookeeper_voltpatches.CreateMode; import org.apache.zookeeper_voltpatches.KeeperException; import org.apache.zookeeper_voltpatches.ZooKeeper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.voltcore.logging.VoltLogger; public class StateMachineSnipits extends ZKTestBase { VoltLogger log = new VoltLogger("HOST"); private final int NUM_AGREEMENT_SITES = 4; private final String stateMachineManagerRoot = "/test/db/statemachine"; @Before public void setUp() throws Exception { setUpZK(NUM_AGREEMENT_SITES); ZooKeeper zk = m_messengers.get(0).getZK(); ZKUtil.addIfMissing(zk, "/test", CreateMode.PERSISTENT, null); ZKUtil.addIfMissing(zk, "/test/db", CreateMode.PERSISTENT, null); ZKUtil.addIfMissing(zk, stateMachineManagerRoot, CreateMode.PERSISTENT, null); } @After public void tearDown() throws Exception { tearDownZK(); } public void failSite(int site) throws Exception { m_messengers.get(site).shutdown(); m_messengers.set(site, null); } public SynchronizedStatesManager addStateMachineManagerFor(int siteId, String communityName, String managerId, int numberOfStateMachines) { SynchronizedStatesManager ssm = null; try { assert(siteId < NUM_AGREEMENT_SITES); ssm = new SynchronizedStatesManager(m_messengers.get(siteId).getZK(), stateMachineManagerRoot, communityName, managerId, numberOfStateMachines); } catch (KeeperException | InterruptedException e) { } catch (Exception e) { } return ssm; } class DistributedLock extends SynchronizedStatesManager.StateMachineInstance { public boolean m_locked = false; private Lock m_mutex; public DistributedLock(SynchronizedStatesManager ssm, String instanceName) { ssm.super(instanceName, log); } @Override protected String stateToString(ByteBuffer state) { return ""; } @Override protected ByteBuffer notifyOfStateMachineReset(boolean isDirectVictim) { return ByteBuffer.allocate(0); } @Override protected void setInitialState(ByteBuffer currentAgreedState) { } @Override protected void lockRequestCompleted() { // This won't always work because of a race between a cancel and a complete // but you get the idea. if (m_mutex == null) return; synchronized(m_mutex) { m_locked = true; m_mutex.notifyAll(); } } public void acquireDistriutedLock() { assert(!m_locked); assert(m_mutex == null); m_mutex = new ReentrantLock(); synchronized(m_mutex) { if (!requestLock()) { try { while(!m_locked) { m_mutex.wait(); } } catch (InterruptedException e) { } } else { m_locked = true; } } } public void start() throws InterruptedException { registerStateMachineWithManager(ByteBuffer.allocate(0)); } // With this you can monitor the lock without blocking public boolean acquireDistributedLockAsync(Lock passedMutex) { assert(m_mutex == null); assert(!m_locked); m_mutex = passedMutex; synchronized(m_mutex) { m_locked = requestLock(); } return m_locked; } public void releaseDistributedLock() { assert(m_mutex != null); assert(m_locked); cancelLockRequest(); synchronized(m_mutex) { m_locked = false; } m_mutex = null; } } private interface MemberChangeCallback { void membersAdded(Set<String> addedMembers); void membersRemoved(Set<String> removedMembers); void membersAddedAndRemoved(Set<String> addedMembers, Set<String> removedMembers); } public class MembershipChangeMonitor extends SynchronizedStatesManager.StateMachineInstance { protected MemberChangeCallback m_cb = null; public MembershipChangeMonitor(SynchronizedStatesManager ssm, String instanceName) { ssm.super(instanceName, log); } @Override protected void membershipChanged(Set<String> addedMembers, Set<String> removedMembers) { if (m_cb != null) { if (addedMembers.isEmpty()) { if (!removedMembers.isEmpty()) { m_cb.membersRemoved(removedMembers); } } else if (removedMembers.isEmpty()) { m_cb.membersAdded(addedMembers); } else { m_cb.membersAddedAndRemoved(addedMembers, removedMembers);; } } } @Override protected ByteBuffer notifyOfStateMachineReset(boolean isDirectVictim) { return ByteBuffer.allocate(0); } @Override protected void setInitialState(ByteBuffer currentAgreedState) { m_cb.membersAdded(getCurrentMembers()); } @Override protected String stateToString(ByteBuffer state) { return ""; } /* * Returns the most accurate set of members. Note that the returned set may include or * members for which a notification has not yet been provided through the * MemberChangeCallback. */ public Set<String> getKnownMembers() { return getCurrentMembers(); } /* * Returns the memberId of the StateMachineManager this instance is registered with. */ public String GetMyMemberId() { return getMemberId(); } } private interface StateMachineCallback { void stateMachineInitialized(); void lockRequestGranted(); void stateChangeProposed(ByteBuffer newState); void stateChanged(); void ourProposalAccepted(); void ourProposalRejected(); } public abstract class StateMachine extends SynchronizedStatesManager.StateMachineInstance { private ByteBuffer m_startingState; protected volatile ByteBuffer m_currentStateBuffer = null; protected StateMachineCallback m_cb; private final AtomicBoolean m_proposalPending = new AtomicBoolean(false); private boolean m_haveLock = false; protected StateMachine(SynchronizedStatesManager ssm, String instanceName) { ssm.super(instanceName, log); } @Override protected ByteBuffer notifyOfStateMachineReset(boolean isDirectVictim) { return m_startingState; } @Override protected void setInitialState(ByteBuffer currentAgreedState) { m_currentStateBuffer = currentAgreedState; m_cb.stateMachineInitialized(); } @Override protected void lockRequestCompleted() { m_haveLock = true; m_cb.lockRequestGranted(); } @Override protected void stateChangeProposed(ByteBuffer proposedState) { assert(!m_haveLock); boolean wasProposing = m_proposalPending.getAndSet(true); assert(!wasProposing); m_cb.stateChangeProposed(proposedState); } @Override protected void proposedStateResolved(boolean ourProposal, ByteBuffer proposedState, boolean success) { if (ourProposal) { if (success) { m_currentStateBuffer = proposedState; m_cb.ourProposalAccepted(); } else { m_cb.ourProposalRejected(); } m_haveLock = false; ourProposal = false; } else { if (success) { m_currentStateBuffer = proposedState; m_cb.stateChanged(); } } m_proposalPending.set(false); } protected void setDefaultState(ByteBuffer defaultState) { m_startingState = defaultState; } public void start() throws InterruptedException { registerStateMachineWithManager(m_startingState); } public ByteBuffer getCurrentStateBuff() { return m_currentStateBuffer; } public void attemptStateChange() { assert(isInitialized()); assert(!m_haveLock); if (requestLock()) { m_haveLock = true; m_cb.lockRequestGranted(); } } public void cancelAttempt() { assert(isInitialized()); assert(m_haveLock); m_haveLock = false; cancelLockRequest(); } public void proposeChange(ByteBuffer proposal) { assert(isInitialized()); assert(m_haveLock); boolean wasProposing = m_proposalPending.getAndSet(true); assert(!wasProposing); proposeStateChange(proposal); } } private interface TaskCallback { public void lockRequestGranted(); public void processTask(ByteBuffer newTask); public void ourCorrelatedTaskComplete(Map<String, ByteBuffer> results); public void ourUncorrelatedTaskComplete(List<ByteBuffer> results); public void externalCorrelatedTaskComplete(Map<String, ByteBuffer> results); public void externalUncorrelatedTaskComplete(List<ByteBuffer> results); } public abstract class Task extends SynchronizedStatesManager.StateMachineInstance { protected TaskCallback m_cb; private final AtomicBoolean m_taskPending = new AtomicBoolean(false); private boolean m_haveLock = false; public Task(SynchronizedStatesManager ssm, String instanceName) { ssm.super(instanceName, log); } @Override protected ByteBuffer notifyOfStateMachineReset(boolean isDirectVictim) { return ByteBuffer.allocate(0); } @Override protected void setInitialState(ByteBuffer currentAgreedState) { } @Override protected void lockRequestCompleted() { m_haveLock = true; m_cb.lockRequestGranted(); } @Override protected void taskRequested(ByteBuffer proposedTask) { boolean hadTask = m_taskPending.getAndSet(true); assert(!hadTask); m_cb.processTask(proposedTask); } @Override protected void correlatedTaskCompleted(boolean ourTask, ByteBuffer taskRequest, Map<String, ByteBuffer> results) { boolean processingTask = m_taskPending.getAndSet(false); assert(processingTask); if (ourTask) { assert(m_haveLock); m_cb.ourCorrelatedTaskComplete(results); m_haveLock = false; } else { m_cb.externalCorrelatedTaskComplete(results); } } @Override protected void uncorrelatedTaskCompleted(boolean ourTask, ByteBuffer taskRequest, List<ByteBuffer> results) { boolean processingTask = m_taskPending.getAndSet(false); assert(processingTask); if (ourTask) { assert(m_haveLock); m_cb.ourUncorrelatedTaskComplete(results); m_haveLock = false; } else { m_cb.externalUncorrelatedTaskComplete(results); } } public void attemptTask() { assert(isInitialized()); assert(!m_haveLock); if (requestLock()) { m_haveLock = true; m_cb.lockRequestGranted(); } } public void cancelAttempt() { assert(isInitialized()); assert(m_haveLock); m_haveLock = false; cancelLockRequest(); } public void startTask(boolean correlated, ByteBuffer proposedTask) { assert(isInitialized()); assert(m_haveLock); initiateCoordinatedTask(correlated, proposedTask); } public void supplyResponse(ByteBuffer taskResponse) { requestedTaskComplete(taskResponse); } } @Test public void testLockingExample() { // Create a state machine manager operating in the LOCK_TESTER domain that will run on ZooKeeper0. // This manager is named manager1 and it expects a single state machine instance. SynchronizedStatesManager ssm1 = addStateMachineManagerFor(0, "LOCK_TESTER", "manager1", 1); // Create a state machine manager operating in the LOCK_TESTER domain that will run on ZooKeeper1. // This manager is named manager2 and it expects a single state machine instance. SynchronizedStatesManager ssm2 = addStateMachineManagerFor(1, "LOCK_TESTER", "manager2", 1); // This is a little convoluted, but because StateMachineInstances are a nested class of // SynchronizedStatesManager we must specify the correct SynchronizedStatesManager in the // constructor. The second parameter is the state machine name shared by all instances wishing // to participate in the same shared machine across Manager instances using the same // domain (LOCK_TESTER). DistributedLock distributedLock1 = new DistributedLock(ssm1, "lockingStateMachine"); try { distributedLock1.start(); } catch (InterruptedException e1) { fail(); } DistributedLock distributedLock2 = new DistributedLock(ssm2, "lockingStateMachine"); try { distributedLock2.start(); } catch (InterruptedException e1) { fail(); } while (!distributedLock1.isInitialized()) { Thread.yield(); } while (!distributedLock2.isInitialized()) { Thread.yield(); } distributedLock1.acquireDistriutedLock(); Lock waiterForLock2 = new ReentrantLock(); distributedLock2.acquireDistributedLockAsync(waiterForLock2); try { ssm2 = null; failSite(1); distributedLock1.releaseDistributedLock(); } catch (Exception e) { fail(); } // Create a state machine manager operating in the LOCK_TESTER domain that will run on ZooKeeper0. // This manager is named manager3 and it expects a single state machine instance. SynchronizedStatesManager ssm3 = addStateMachineManagerFor(0, "LOCK_TESTER", "manager3", 1); DistributedLock distributedLock3 = new DistributedLock(ssm3, "lockingStateMachine"); try { distributedLock3.start(); } catch (InterruptedException e1) { fail(); } try { // Note that distributedLock3 won't be initialized until distributedLock1 and distributedLock2 both // release their locks. while (!distributedLock3.isInitialized()) { Thread.yield(); } Lock waiterForLock3 = new ReentrantLock(); distributedLock3.acquireDistributedLockAsync(waiterForLock3); synchronized(waiterForLock3) { while (!distributedLock3.m_locked) waiterForLock3.wait(); } waiterForLock3 = null; distributedLock3.releaseDistributedLock(); tearDownZK(); } catch (Exception e) { fail(); } } public class MembershipMonitor extends MembershipChangeMonitor implements MemberChangeCallback { public Set<String> m_members = new HashSet<String>(); public MembershipMonitor(SynchronizedStatesManager ssm, String instanceName) { super(ssm, instanceName); } public void start() throws InterruptedException { m_cb = this; registerStateMachineWithManager(ByteBuffer.allocate(0)); } public void membersAdded(Set<String> addedMembers) { m_members.addAll(addedMembers); } public void membersRemoved(Set<String> removedMembers) { m_members.removeAll(removedMembers); } public void membersAddedAndRemoved(Set<String> addedMembers, Set<String> removedMembers) { m_members.removeAll(removedMembers); m_members.addAll(addedMembers); } public boolean hasIdenticalMembership(MembershipMonitor membership) { if ( m_members.size() != membership.m_members.size() ) { return false; } HashSet<String> clone = new HashSet<String>(membership.m_members); // just use h2 if you don't need to save the original h2 Iterator<String> it = m_members.iterator(); while (it.hasNext() ){ String member = it.next(); if (clone.contains(member)){ // replace clone with h2 if not concerned with saving data from h2 clone.remove(member); } else { return false; } } return true; // will only return true if sets are equal } } @Test public void testMembershipExample() { // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper0. // This manager is named monitor1 and it expects a single state machine instance. SynchronizedStatesManager ssm1 = addStateMachineManagerFor(0, "COMMUNITY", "monitor1", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper1. // This manager is named monitor2 and it expects a single state machine instance. SynchronizedStatesManager ssm2 = addStateMachineManagerFor(1, "COMMUNITY", "monitor2", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper0. // This manager is named monitor3 and it expects a single state machine instance. SynchronizedStatesManager ssm3 = addStateMachineManagerFor(0, "COMMUNITY", "monitor3", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper2. // This manager is named monitor4 and it expects a single state machine instance. SynchronizedStatesManager ssm4 = addStateMachineManagerFor(2, "COMMUNITY", "monitor4", 1); try { // This is a little convoluted, but because StateMachineInstances are a nested class of // SynchronizedStatesManager we must specify the correct SynchronizedStatesManager in the // constructor. The second parameter is the state machine name shared by all instances wishing // to participate in the same shared state machine across Manager instances using the same // domain (COMMUNITY). MembershipMonitor monitorForManager1 = new MembershipMonitor(ssm1, "memberMonitor"); monitorForManager1.start(); while (!monitorForManager1.isInitialized()) { Thread.yield(); } MembershipMonitor monitorForManager2 = new MembershipMonitor(ssm2, "memberMonitor"); monitorForManager2.start(); while (!monitorForManager2.isInitialized()) { Thread.yield(); } while (!monitorForManager1.hasIdenticalMembership(monitorForManager2)) { Thread.yield(); } assertTrue(monitorForManager1.m_members.size() == 2); // This is sharing the same zookeeper client as monitorForManager1 MembershipMonitor monitorForManager3 = new MembershipMonitor(ssm3, "memberMonitor"); monitorForManager3.start(); while (!monitorForManager3.isInitialized()) { Thread.yield(); } while (!monitorForManager1.hasIdenticalMembership(monitorForManager3) && !monitorForManager1.hasIdenticalMembership(monitorForManager3)) { Thread.yield(); } assertTrue(monitorForManager1.m_members.size() == 3); // Remove MembershipMonitor1 from ZooKeeper0 while keeping MembershipMonitor3 alive ssm1.ShutdownSynchronizedStatesManager(); while (monitorForManager3.m_members.size() != 2 && !monitorForManager2.hasIdenticalMembership(monitorForManager3)) { Thread.yield(); } MembershipMonitor monitorForManager4 = new MembershipMonitor(ssm4, "memberMonitor"); monitorForManager4.start(); while (!monitorForManager4.isInitialized()) { Thread.yield(); } // kill ZooKeeper0, removing monitor3. try { failSite(0); } catch (Exception e) { fail(); } while (monitorForManager4.m_members.size() != 2 && !monitorForManager4.hasIdenticalMembership(monitorForManager2)) { Thread.yield(); } } catch (InterruptedException e) { fail(); } } public class ByteStateChanger extends StateMachine { private volatile boolean m_myChangePending = false; private byte m_currentState; private byte m_pendingState = 0; @Override protected String stateToString(ByteBuffer state) { return Byte.toString(state.get()); } public ByteStateChanger(SynchronizedStatesManager ssm, String instanceName, byte defaultState) { super(ssm, instanceName); setDefaultState(ByteBuffer.wrap(new byte[] {defaultState})); m_cb = new smCallback(); } private class smCallback implements StateMachineCallback { @Override public void stateMachineInitialized() { m_currentState = m_currentStateBuffer.get(); m_currentStateBuffer.rewind(); } @Override public void lockRequestGranted() { assert(m_myChangePending); byte[] arr = new byte[] {m_pendingState}; proposeStateChange(ByteBuffer.wrap(arr)); } @Override public void stateChangeProposed(ByteBuffer newState) { // This could implement an evaluation of the proposed state requestedStateChangeAcceptable(true); } @Override public synchronized void stateChanged() { m_currentState = m_currentStateBuffer.get(); m_currentStateBuffer.rewind(); // We can respond to the notification here if (m_myChangePending && m_currentState == m_pendingState) { m_myChangePending = false; cancelAttempt(); } } @Override public void ourProposalAccepted() { m_currentState = m_pendingState; m_myChangePending = false; } @Override public void ourProposalRejected() { m_myChangePending = false; } } public void changeState(byte proposedState) { m_pendingState = proposedState; m_myChangePending = true; attemptStateChange(); } public boolean changeStillPending() { return m_myChangePending; } public byte getState() { return m_currentState; } } public class ShortStateChanger extends StateMachine { private volatile boolean m_myChangePending = false; private short m_currentState; private short m_pendingState = 0; @Override protected String stateToString(ByteBuffer state) { return Short.toString(state.get()); } public ShortStateChanger(SynchronizedStatesManager ssm, String instanceName, short defaultState) { // assumes big endian super(ssm, instanceName); ByteBuffer defaultStateBuff = ByteBuffer.allocate(2).putShort(defaultState); defaultStateBuff.flip(); setDefaultState(defaultStateBuff); m_cb = new smCallback(); } private class smCallback implements StateMachineCallback { @Override public void stateMachineInitialized() { m_currentState = m_currentStateBuffer.getShort(); m_currentStateBuffer.rewind(); } @Override public void lockRequestGranted() { assert(m_myChangePending); ByteBuffer pending = ByteBuffer.allocate(2); pending.putShort(m_pendingState); pending.flip(); proposeStateChange(pending); } @Override public void stateChangeProposed(ByteBuffer newState) { // This could implement an evaluation of the proposed state requestedStateChangeAcceptable(true); } @Override public synchronized void stateChanged() { m_currentState = m_currentStateBuffer.getShort(); m_currentStateBuffer.rewind(); // We can respond to the notification here if (m_myChangePending && m_currentState == m_pendingState) { m_myChangePending = false; cancelAttempt(); } } @Override public void ourProposalAccepted() { m_currentState = m_pendingState; m_myChangePending = false; } @Override public void ourProposalRejected() { m_myChangePending = false; } } public void changeState(short proposedState) { m_pendingState = proposedState; m_myChangePending = true; attemptStateChange(); } public boolean changeStillPending() { return m_myChangePending; } public short getState() { return m_currentState; } } @Test public void testStateChangeExample() { byte[] byteStates = new byte[] {5, 23, 54, 92, 118, 122}; short[] shortStates = new short[] {5000, 6000, 7000, 8000, 9000, 10000}; // Create a state machine manager operating in the COMMUNITY1 domain that will run on ZooKeeper0. // This manager is named manager1 and it expects 2 state machine instances. SynchronizedStatesManager ssmC1M1 = addStateMachineManagerFor(0, "COMMUNITY1", "manager1", 2); // Create a state machine manager operating in the COMMUNITY2 domain that will run on ZooKeeper1. // This manager is named manager1 and it expects 1 state machine instance. SynchronizedStatesManager ssmC2M1 = addStateMachineManagerFor(1, "COMMUNITY2", "manager1", 1); // Create a state machine manager operating in the COMMUNITY2 domain that will run on ZooKeeper0. // This manager is named manager2 and it expects 1 state machine instance. SynchronizedStatesManager ssmC2M2 = addStateMachineManagerFor(0, "COMMUNITY2", "manager2", 1); // Create a state machine manager operating in the COMMUNITY2 domain that will run on ZooKeeper0. // This manager is named manager2 and it expects 2 state machine instance.s SynchronizedStatesManager ssmC1M2 = addStateMachineManagerFor(2, "COMMUNITY1", "manager2", 2); try { // Initialize ssmC1M1 with a byte (with 5) and short (with 7000) state machine ByteStateChanger bC1M1 = new ByteStateChanger(ssmC1M1, "byteMachine", byteStates[0]); ShortStateChanger sC1M1 = new ShortStateChanger(ssmC1M1, "shortMachine", shortStates[2]); bC1M1.start(); sC1M1.start(); while (!bC1M1.isInitialized() && !sC1M1.isInitialized()) { Thread.yield(); } assertTrue(bC1M1.m_currentState == byteStates[0]); assertTrue(sC1M1.m_currentState == shortStates[2]); // Initialize ssmC2M1 with a short state machine that will not participate in the same domain as sC1M1 ShortStateChanger sC2M1 = new ShortStateChanger(ssmC2M1, "shortMachine", shortStates[1]); sC2M1.start(); while (!sC2M1.isInitialized()) { Thread.yield(); } assertTrue(sC2M1.m_currentState == shortStates[1]); // Initialize ssmC2M2 with a short state machine that will not participate in the same domain as sC1M1 ShortStateChanger sC2M2 = new ShortStateChanger(ssmC2M2, "shortMachine", shortStates[3]); sC2M2.start(); while (!sC2M2.isInitialized()) { Thread.yield(); } assertTrue(sC2M2.m_currentState == sC2M1.m_currentState && sC2M2.m_currentState == shortStates[1]); // Change the state of sC2M2 and verify it is changed in sC2M1 sC2M2.changeState(shortStates[0]); while (sC2M2.changeStillPending() && sC2M2.getState() != sC2M1.getState()) { Thread.yield(); } assertFalse(sC2M2.getState() == sC1M1.getState()); // Now we will add the byte and short state machines to synchronize with bC1M1 and sC1M1 respectively ByteStateChanger bC1M2 = new ByteStateChanger(ssmC1M2, "byteMachine", byteStates[4]); ShortStateChanger sC1M3 = new ShortStateChanger(ssmC1M2, "shortMachine", shortStates[4]); bC1M2.start(); sC1M3.start(); while (!bC1M2.isInitialized() && !sC1M3.isInitialized()) { Thread.yield(); } assertTrue(bC1M2.getState() == byteStates[0] && sC1M3.getState() == shortStates[2]); // Make 2 state changes in a row from 2 different members and verify the final state is the last one bC1M2.changeState(byteStates[0]); bC1M1.changeState(byteStates[4]); while (bC1M1.changeStillPending() && bC1M1.getState() == bC1M2.getState()) { Thread.yield(); } } catch (InterruptedException e) { fail(); } } protected class TaskPerformanceAnalyzer extends Task implements TaskCallback { Date m_proposersStartTime; Date m_receiveTaskTime; Date m_taskCompleteTime; volatile long m_slowestResponseTime; long m_fastestResponseTime; String m_slowestResponder; String m_fastestResponder; protected String stateToString(ByteBuffer state) { return ""; } protected String taskToString(ByteBuffer task) { return "Start Timed Task"; } public TaskPerformanceAnalyzer(SynchronizedStatesManager ssm, String instanceName) { super(ssm, instanceName); } public void lockRequestGranted() { ByteBuffer taskRequest = ByteBuffer.allocate(8); taskRequest.putLong(new Date().getTime()); taskRequest.flip(); // We are only demonstrating correlated tasks startTask(true, taskRequest); } public void processTask(ByteBuffer newTask) { m_slowestResponseTime = 0; m_proposersStartTime = new Date(newTask.getLong()); ByteBuffer taskResponse = ByteBuffer.allocate(8); m_receiveTaskTime = new Date(); taskResponse.putLong(m_receiveTaskTime.getTime()); taskResponse.flip(); supplyResponse(taskResponse); } public void ourUncorrelatedTaskComplete(List<ByteBuffer> results) { // This would allow you to determine the slowest responder } public void externalUncorrelatedTaskComplete(List<ByteBuffer> results) { // This would allow you to determine the slowest responder } private void getLongestResponseTime(Map<String, ByteBuffer> results) { m_taskCompleteTime = new Date(); long slowest = 0; long fastest = Long.MAX_VALUE; for (Map.Entry<String, ByteBuffer> result : results.entrySet()) { long responseTime = result.getValue().getLong(); if (responseTime > slowest) { m_slowestResponder = result.getKey(); slowest = responseTime; } if (responseTime < fastest) { m_fastestResponder = result.getKey(); fastest = responseTime; } } m_fastestResponseTime = fastest - m_proposersStartTime.getTime(); m_slowestResponseTime = slowest - m_proposersStartTime.getTime(); } public void ourCorrelatedTaskComplete(Map<String, ByteBuffer> results) { getLongestResponseTime(results); } public void externalCorrelatedTaskComplete(Map<String, ByteBuffer> results) { getLongestResponseTime(results); } public void start() throws InterruptedException { registerStateMachineWithManager(ByteBuffer.allocate(0)); m_cb = this; } public void startTimerCalculation() { m_slowestResponseTime = 0; attemptTask(); } public boolean taskDone() { return m_slowestResponseTime != 0; } public long getRequestDelay() { return m_receiveTaskTime.getTime() - m_proposersStartTime.getTime(); } public long getResponseDelay() { return m_taskCompleteTime.getTime() - m_receiveTaskTime.getTime(); } public long getRoundTripDelay() { return m_taskCompleteTime.getTime() - m_proposersStartTime.getTime(); } } @Test public void testTimingTask() { // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper0. // This manager is named timer1 and it expects 1 state machine instance. SynchronizedStatesManager ssm1 = addStateMachineManagerFor(0, "COMMUNITY", "timer1", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper1. // This manager is named timer2 and it expects a 1 state machine instance. SynchronizedStatesManager ssm2 = addStateMachineManagerFor(1, "COMMUNITY", "timer2", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper0. // This manager is named timer3 and it expects 1 state machine instance. SynchronizedStatesManager ssm3 = addStateMachineManagerFor(0, "COMMUNITY", "timer3", 1); // Create a state machine manager operating in the COMMUNITY domain that will run on ZooKeeper2. // This manager is named timer4 and it expects 1 state machine instance. SynchronizedStatesManager ssm4 = addStateMachineManagerFor(2, "COMMUNITY", "timer4", 1); TaskPerformanceAnalyzer tpa1 = new TaskPerformanceAnalyzer(ssm1, "performanceStateMachine"); TaskPerformanceAnalyzer tpa2 = new TaskPerformanceAnalyzer(ssm2, "performanceStateMachine"); TaskPerformanceAnalyzer tpa3 = new TaskPerformanceAnalyzer(ssm3, "performanceStateMachine"); TaskPerformanceAnalyzer tpa4 = new TaskPerformanceAnalyzer(ssm4, "performanceStateMachine"); try { tpa1.start(); tpa2.start(); tpa3.start(); tpa4.start(); while (!tpa1.isInitialized() || !tpa2.isInitialized() || !tpa3.isInitialized() || !tpa4.isInitialized()) { Thread.yield(); } tpa1.startTimerCalculation(); while (!tpa1.taskDone() || !tpa2.taskDone() || !tpa3.taskDone() || !tpa4.taskDone()) { Thread.yield(); } System.out.println("SM Id | Request | Response | roundTrip"); System.out.printf(" 1 %5d %5d %5d\n", tpa1.getRequestDelay(), tpa1.getResponseDelay(), tpa1.getRoundTripDelay()); System.out.printf(" 2 %5d %5d %5d\n", tpa2.getRequestDelay(), tpa2.getResponseDelay(), tpa2.getRoundTripDelay()); System.out.printf(" 3 %5d %5d %5d\n", tpa3.getRequestDelay(), tpa3.getResponseDelay(), tpa3.getRoundTripDelay()); System.out.printf(" 4 %5d %5d %5d\n", tpa4.getRequestDelay(), tpa4.getResponseDelay(), tpa4.getRoundTripDelay()); } catch (InterruptedException e) { fail(); } } }