/* * Copyright 2014, The Sporting Exchange Limited * * 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.betfair.cougar.client.socket; import com.betfair.cougar.core.api.ev.ConnectedResponse; import com.betfair.cougar.core.api.ev.Subscription; import com.betfair.cougar.core.api.ev.WaitingObserver; import com.betfair.cougar.netutil.nio.HeapDelta; import com.betfair.cougar.netutil.nio.NioLogger; import com.betfair.cougar.netutil.nio.NioUtils; import com.betfair.cougar.netutil.nio.TerminateSubscription; import com.betfair.cougar.netutil.nio.connected.*; import com.betfair.cougar.test.ParameterizedMultiRunner; import com.betfair.cougar.transport.api.protocol.CougarObjectIOFactory; import com.betfair.cougar.transport.api.protocol.CougarObjectOutput; import com.betfair.cougar.transport.api.protocol.socket.InvocationResponse; import com.betfair.cougar.transport.api.protocol.socket.NewHeapSubscription; import com.betfair.cougar.transport.socket.InvocationResponseImpl; import com.betfair.platform.virtualheap.Heap; import com.betfair.platform.virtualheap.HeapListener; import com.betfair.platform.virtualheap.NodeType; import com.betfair.platform.virtualheap.updates.UpdateBlock; import org.apache.mina.common.IoSession; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.OutputStream; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import static junit.framework.Assert.*; import static org.mockito.Mockito.*; @RunWith(value = ParameterizedMultiRunner.class) public class ClientConnectedObjectManagerTest { private ClientConnectedObjectManager subject; private int ioSessionId = 1; private int numThreads; private CougarObjectIOFactory objectIOFactory; public ClientConnectedObjectManagerTest(int numThreads) { this.numThreads = numThreads; } @ParameterizedMultiRunner.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{{1},{2}}); } @BeforeClass public static void multiSetup() { ParameterizedMultiRunner.setNumRuns(Integer.parseInt(System.getProperty("connectedObjects.numTestRuns","1"))); } @Before public void before() { subject = new ClientConnectedObjectManager(); subject.setMaxInitialPopulationWait(50L); subject.setPullerAwaitTimeout(20L); subject.setMissingDeltaTimeout(20L); subject.setMaxDeltaQueue(10); subject.setNioLogger(new NioLogger("TRANSPORT")); subject.setNumProcessingThreads(numThreads); objectIOFactory = mock(CougarObjectIOFactory.class); subject.setObjectIOFactory(objectIOFactory); subject.start(); } @After public void after() { subject.stop(); } @Test public void firstSubscription() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "firstSubscription"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta delta = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, delta); waitForAndAssertNotFault(observer); Subscription sub = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub); assertNull(sub.getCloseReason()); } @Test public void firstSubscriptionNotReceivingInitialUpdate() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "firstSubscriptionNotReceivingInitialUpdate"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); assertTrue(observer.await(1000L)); assertTrue(observer.getExecutionResult().isFault()); assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); } @Test public void firstSubscriptionInitialUpdateReceivedLate() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "firstSubscriptionInitialUpdateReceivedLate"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); assertTrue(observer.await(1000L)); assertTrue(observer.getExecutionResult().isFault()); HeapDelta delta = new HeapDelta(1, 0, createUpdateList(createInitial(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, true)))); subject.applyDelta(session, delta); assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); } @Test public void secondSubscriptionToSameHeap() throws Exception { IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondSubscriptionToSameHeap"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta delta = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, delta); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session, response2, observer2); waitForAndAssertNotFault(observer2); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); } @Test public void twoSubscriptionsToDifferentHeaps() throws Exception { IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "twoSubscriptionsToDifferentHeaps-1"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); subject.applyDelta(session, new HeapDelta(1, 0, createUpdateList(createInitial()))); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(2, "sub2", "twoSubscriptionsToDifferentHeaps-2"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session, response2, observer2); subject.applyDelta(session, new HeapDelta(2, 0, createUpdateList(createInitial()))); waitForAndAssertNotFault(observer2); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); } @Test public void basicUpdate() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "basicUpdate"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); } @Test public void basicMultiUpdate() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "basicMultiUpdate"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(3); heap.addListener(listener, false); subject.applyDelta(session, new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1))))); subject.applyDelta(session, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, 2))))); subject.applyDelta(session, new HeapDelta(1, 3, createUpdateList(createUpdate(new SetScalar(0, 3))))); assertTrue(listener.waitForEnd(1000L)); } @Test public void basicUpdateFromTwoSessions() throws Exception { // session1 - initial sub IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "basicUpdateFromTwoSessions"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); // session2 - initial sub IoSession session2 = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2", "basicUpdateFromTwoSessions"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session2, response2, observer2); HeapDelta initial2 = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session2, initial2); // wait for initial completions waitForAndAssertNotFault(observer); waitForAndAssertNotFault(observer2); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); // session1 - basic update Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); // session2 - basic update Heap heap2 = ((ConnectedResponse) observer2.getExecutionResult().getResult()).getHeap(); WaitingListener listener2 = new WaitingListener(1); heap2.addListener(listener2, false); HeapDelta delta2 = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session2, delta2); // wait for basic updates assertTrue(listener.waitForEnd(1000L)); assertTrue(listener2.waitForEnd(1000L)); } @Test public void basicMultiUpdateToTwoSessions() throws Exception { // session1 - initial sub IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "basicMultiUpdateToTwoSessions"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); // session2 - initial sub IoSession session2 = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2", "basicMultiUpdateToTwoSessions"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session2, response2, observer2); HeapDelta initial2 = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session2, initial2); // wait for initial completions waitForAndAssertNotFault(observer); waitForAndAssertNotFault(observer2); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); // session1 - basic update Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(3); heap.addListener(listener, false); subject.applyDelta(session, new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1))))); subject.applyDelta(session, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, 2))))); subject.applyDelta(session, new HeapDelta(1, 3, createUpdateList(createUpdate(new SetScalar(0, 3))))); // session2 - basic update Heap heap2 = ((ConnectedResponse) observer2.getExecutionResult().getResult()).getHeap(); WaitingListener listener2 = new WaitingListener(3); heap2.addListener(listener2, false); subject.applyDelta(session2, new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1))))); subject.applyDelta(session2, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, 2))))); subject.applyDelta(session2, new HeapDelta(1, 3, createUpdateList(createUpdate(new SetScalar(0, 3))))); // wait for basic updates assertTrue(listener.waitForEnd(1000L)); assertTrue(listener2.waitForEnd(1000L)); } @Test public void addSubscriptionMidStream() throws Exception { // sub1 - initial sub IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "addSubscriptionMidStream"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); // wait for initial completion waitForAndAssertNotFault(observer); // sub1 - basic update (1) Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); subject.applyDelta(session, new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1))))); // sub2 - initial sub NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2", "1"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session, response2, observer2); // wait for initial completion waitForAndAssertNotFault(observer2); Heap heap2 = ((ConnectedResponse) observer2.getExecutionResult().getResult()).getHeap(); WaitingListener listener2 = new WaitingListener(2); heap2.addListener(listener2, false); // more updates (for both listener.resetLatch(2); subject.applyDelta(session, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, 2))))); subject.applyDelta(session, new HeapDelta(1, 3, createUpdateList(createUpdate(new SetScalar(0, 3))))); // wait for basic updates assertTrue(listener.waitForEnd(1000L)); assertTrue(listener2.waitForEnd(1000L)); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); } @Test public void heapTerminationMidStream() throws Exception { // we don't need to test termination at the beginning as this is picked up by the server and returned as an operation exception on subscription NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "heapTerminationMidStream"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); Lock heapSubMutationLock = subject.getHeapSubMutationLock(); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new TerminateHeap()))); subject.applyDelta(session, delta); assertTrue(listener.waitForHeapTermination(1000L)); // now wait for it to exit the lock, so we can check it's done it's work try { heapSubMutationLock.lock(); } finally { heapSubMutationLock.unlock(); } assertEquals(Subscription.CloseReason.REQUESTED_BY_PUBLISHER, sub1.getCloseReason()); // heap termination should trigger cleanup.. assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); } @Test public void sessionClosed() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "basicUpdate"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); assertNotNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); subject.sessionTerminated(session); assertEquals(Subscription.CloseReason.CONNECTION_CLOSED, sub1.getCloseReason()); assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); } @Test public void oneOfTwoSessionsClosed() throws Exception { // session1 - initial sub IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "oneOfTwoSessionsClosed1"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); // session2 - initial sub IoSession session2 = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2", "oneOfTwoSessionsClosed"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session2, response2, observer2); HeapDelta initial2 = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session2, initial2); // wait for initial completions waitForAndAssertNotFault(observer); waitForAndAssertNotFault(observer2); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); assertNotNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); assertNotNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session2))); subject.sessionTerminated(session); assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); assertEquals(Subscription.CloseReason.CONNECTION_CLOSED, sub1.getCloseReason()); assertNotNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session2))); assertNull(sub2.getCloseReason()); } @Test public void exceptionInPuller() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "exceptionInPuller"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta mockedDelta = mock(HeapDelta.class); // when(mockedDelta.containsFirstUpdate()).thenThrow(new NullPointerException()); when(mockedDelta.getHeapId()).thenReturn(1L); when(mockedDelta.getUpdateId()).thenReturn(2L); Lock heapUpdateLock = subject.getHeapsByServer().get(NioUtils.getSessionId(session)).getHeapState(1).getHeapUpdateLock(); final CountDownLatch latch = new CountDownLatch(1); when(mockedDelta.containsFirstUpdate()).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); throw new NullPointerException(); } }); subject.applyDelta(session, mockedDelta); // wait for our exception method to be called latch.await(1000, TimeUnit.MILLISECONDS); // now wait for it to exit the lock, so we can check it's done it's work try { heapUpdateLock.lock(); } finally { heapUpdateLock.unlock(); } // heap termination should trigger cleanup.. assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); assertEquals(Subscription.CloseReason.INTERNAL_ERROR, sub1.getCloseReason()); } @Test public void secondNewHeapSubscription() throws Exception { // if you get a second newheapsub sent then it shouldn't stop updates for previous connected objects.. NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondNewHeapSubscription"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap1 = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener1 = new WaitingListener(1); heap1.addListener(listener1, false); // second new heap message newHeapSubscription = new NewHeapSubscription(1, "sub2", "secondNewHeapSubscription"); response = new InvocationResponseImpl(newHeapSubscription); observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); waitForAndAssertNotFault(observer); Subscription sub2 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); Heap heap2 = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener2 = new WaitingListener(1); heap2.addListener(listener2, false); // now fire in the update HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); // this is going to be no prob assertTrue(listener2.waitForEnd(1000L)); // this is the interesting one.. assertTrue(listener1.waitForEnd(1000L)); assertFalse(sub1.equals(sub2)); } @Test public void secondNewHeapSubscriptionWithSameSubId() throws Exception { // if you get a second newheapsub sent then it shouldn't stop updates for previous connected objects.. NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondNewHeapSubscription"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap1 = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener1 = new WaitingListener(1); heap1.addListener(listener1, false); // second new heap message newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondNewHeapSubscription"); response = new InvocationResponseImpl(newHeapSubscription); observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); // initial = new HeapDelta(1, 0, createUpdateList(createInitial())); // subject.applyDelta(session, initial); waitForAndAssertFault(observer); Subscription sub2 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNull(sub2); // heap termination should trigger cleanup.. assertFalse(session.isConnected()); // this doesn't happen as the listeners don't get called in these tests //assertEquals(Subscription.CloseReason.CONNECTION_CLOSED, sub1.getCloseReason()); } @Test public void delayedDelta() throws Exception { // if the delta queue for a heap grows too long then we've lost a message and need to abort/disconnect NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "delayedDelta"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); MyIoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); // miss out update 2 subject.applyDelta(session, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, true))))); // timeout is 20ms + puller timeout of 20ms, but tests sometimes break with a 50ms timeout.. // disconnection should trigger cleanup.. awaitRemoval(subject.getHeapsByServer(), NioUtils.getSessionId(session), 1000); assertEquals(Subscription.CloseReason.INTERNAL_ERROR, sub1.getCloseReason()); } private <T> void awaitRemoval(Map<T, ? extends Object> map, T key, long millis) { long expiryTime = System.currentTimeMillis() + millis; while (System.currentTimeMillis() < expiryTime) { if (!map.containsKey(key)) { return; } try { Thread.sleep(1); } catch (InterruptedException ie) {} } fail("Key value ("+key+") not removed within "+millis+"ms, value is: "+map.get(key)); } @Test public void lostDelta() throws Exception { // if a delta goes missing for too long (ie a gap) we need to abort/disconnect NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "lostDelta"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); MyIoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); // miss out update 2, max queue is 10, so lets send 11 updates subject.applyDelta(session, new HeapDelta(1, 2, createUpdateList(createUpdate(new SetScalar(0, true))))); subject.applyDelta(session, new HeapDelta(1, 3, createUpdateList(createUpdate(new SetScalar(0, false))))); subject.applyDelta(session, new HeapDelta(1, 4, createUpdateList(createUpdate(new SetScalar(0, true))))); subject.applyDelta(session, new HeapDelta(1, 5, createUpdateList(createUpdate(new SetScalar(0, false))))); subject.applyDelta(session, new HeapDelta(1, 6, createUpdateList(createUpdate(new SetScalar(0, true))))); subject.applyDelta(session, new HeapDelta(1, 7, createUpdateList(createUpdate(new SetScalar(0, false))))); subject.applyDelta(session, new HeapDelta(1, 8, createUpdateList(createUpdate(new SetScalar(0, true))))); subject.applyDelta(session, new HeapDelta(1, 9, createUpdateList(createUpdate(new SetScalar(0, false))))); subject.applyDelta(session, new HeapDelta(1, 10, createUpdateList(createUpdate(new SetScalar(0, true))))); subject.applyDelta(session, new HeapDelta(1, 11, createUpdateList(createUpdate(new SetScalar(0, false))))); subject.applyDelta(session, new HeapDelta(1, 12, createUpdateList(createUpdate(new SetScalar(0, true))))); // now wait 2x the await timeout, which handily is 20ms, for everything to queue up nicely and be processed Thread.sleep(40); // disconnection should trigger cleanup.. assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); assertEquals(Subscription.CloseReason.INTERNAL_ERROR, sub1.getCloseReason()); } @Test public void secondSubscriptionClosedByPublisher() throws Exception { IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondSubscriptionClosedByPublisher"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session, response2, observer2); waitForAndAssertNotFault(observer2); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); subject.terminateSubscription(session, new TerminateSubscription(1, "sub2", Subscription.CloseReason.REQUESTED_BY_PUBLISHER.name())); assertNull(sub1.getCloseReason()); assertEquals(Subscription.CloseReason.REQUESTED_BY_PUBLISHER, sub2.getCloseReason()); } @Test public void lastSubscriptionClosedByPublisher() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "lastSubscriptionClosedByPublisher"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); subject.terminateSubscription(session, new TerminateSubscription(1, "sub1", Subscription.CloseReason.REQUESTED_BY_PUBLISHER.name())); assertEquals(Subscription.CloseReason.REQUESTED_BY_PUBLISHER, sub1.getCloseReason()); } @Test public void nonExistingSubscriptionClosedByPublisher() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "nonExistingSubscriptionClosedByPublisher"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); IoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); // mostly checking this doesn't throw an exception.. subject.terminateSubscription(session, new TerminateSubscription(1, "sub2", Subscription.CloseReason.REQUESTED_BY_PUBLISHER.name())); assertNull(sub1.getCloseReason()); } @Test public void secondSubscriptionClosedBySubscriber() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "secondSubscriptionClosedBySubscriber"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); MyIoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); NewHeapSubscription newHeapSubscription2 = new NewHeapSubscription(1, "sub2"); InvocationResponse response2 = new InvocationResponseImpl(newHeapSubscription2); WaitingObserver observer2 = new WaitingObserver(); subject.handleSubscriptionResponse(session, response2, observer2); waitForAndAssertNotFault(observer2); Subscription sub2 = getSubscriptionFrom(observer2.getExecutionResult().getResult()); assertNotNull(sub2); assertNull(sub2.getCloseReason()); assertFalse(sub1.equals(sub2)); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); CougarObjectOutput coo = mock(CougarObjectOutput.class); when(objectIOFactory.newCougarObjectOutput(any(OutputStream.class), anyByte())).thenReturn(coo); sub2.close(); assertEquals(Subscription.CloseReason.REQUESTED_BY_SUBSCRIBER, sub2.getCloseReason()); assertNull(sub1.getCloseReason()); } @Test public void lastSubscriptionClosedBySubscriber() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "lastSubscriptionClosedBySubscriber"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); MyIoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); CougarObjectOutput coo = mock(CougarObjectOutput.class); when(objectIOFactory.newCougarObjectOutput(any(OutputStream.class), anyByte())).thenReturn(coo); sub1.close(); assertEquals(Subscription.CloseReason.REQUESTED_BY_SUBSCRIBER, sub1.getCloseReason()); } @Test public void subscriptionClosedBySubscriberAndNotificationFails() throws Exception { NewHeapSubscription newHeapSubscription = new NewHeapSubscription(1, "sub1", "subscriptionClosedBySubscriberAndNotificationFails"); InvocationResponse response = new InvocationResponseImpl(newHeapSubscription); WaitingObserver observer = new WaitingObserver(); MyIoSession session = new MyIoSession(String.valueOf(ioSessionId++)); subject.handleSubscriptionResponse(session, response, observer); HeapDelta initial = new HeapDelta(1, 0, createUpdateList(createInitial())); subject.applyDelta(session, initial); waitForAndAssertNotFault(observer); Subscription sub1 = getSubscriptionFrom(observer.getExecutionResult().getResult()); assertNotNull(sub1); assertNull(sub1.getCloseReason()); Heap heap = ((ConnectedResponse) observer.getExecutionResult().getResult()).getHeap(); WaitingListener listener = new WaitingListener(1); heap.addListener(listener, false); HeapDelta delta = new HeapDelta(1, 1, createUpdateList(createUpdate(new InstallRoot(0, NodeType.SCALAR), new SetScalar(0, 1)))); subject.applyDelta(session, delta); assertTrue(listener.waitForEnd(1000L)); when(objectIOFactory.newCougarObjectOutput(any(OutputStream.class), anyByte())).thenThrow(new RuntimeException()); sub1.close(); assertEquals(Subscription.CloseReason.REQUESTED_BY_SUBSCRIBER, sub1.getCloseReason()); // can't check this as this is triggered from session closure and our session isn't fully featured. This is tested in the sessionClosed test though.. // assertEquals(Subscription.CloseReason.INTERNAL_ERROR, sub2.getCloseReason()); assertNull(subject.getHeapsByServer().get(NioUtils.getSessionId(session))); assertTrue(session.getCloseFuture().isClosed()); } private Subscription getSubscriptionFrom(Object result) { if (result == null) { return null; } return ((ConnectedResponse) result).getSubscription(); } private void waitForAndAssertNotFault(WaitingObserver observer) throws InterruptedException { assertTrue(observer.await(1000L)); if (observer.getExecutionResult().isFault()) { fail(observer.getExecutionResult().getFault().getMessage()); } } private void waitForAndAssertFault(WaitingObserver observer) throws InterruptedException { assertTrue(observer.await(1000L)); if (!observer.getExecutionResult().isFault()) { fail("Was expecting a fault"); } } private InitialUpdate createInitial(UpdateAction... actions) { Update u = new Update(); u.setActions(createActionsList(actions)); return new InitialUpdate(u); } private Update createUpdate(UpdateAction... actions) { Update u = new Update(); u.setActions(createActionsList(actions)); return u; } private List<UpdateAction> createActionsList(UpdateAction... actions) { // not using this as it gives a list we can't call addAll() on //return Arrays.asList(actions); List<UpdateAction> ret = new ArrayList<UpdateAction>(); Collections.addAll(ret, actions); return ret; } private List<Update> createUpdateList(Update... updates) { List<Update> ret = new ArrayList<Update>(); Collections.addAll(ret, updates); return ret; } private class WaitingListener implements HeapListener { private CountDownLatch latch; private CountDownLatch terminationLatch = new CountDownLatch(1); public WaitingListener(int i) { latch = new CountDownLatch(i); } private WaitingListener() { this(1); } public boolean waitForEnd(long millis) throws InterruptedException { boolean ret = latch.await(millis, TimeUnit.MILLISECONDS); if (!ret) { System.out.println(latch.getCount()); } return ret; } @Override public void applyUpdate(UpdateBlock update) { for (com.betfair.platform.virtualheap.updates.Update u : update.list()) { if (u.getUpdateType() == com.betfair.platform.virtualheap.updates.Update.UpdateType.TERMINATE_HEAP) { terminationLatch.countDown(); } } latch.countDown(); } public void resetLatch(int i) { latch = new CountDownLatch(i); } public boolean waitForHeapTermination(long millis) throws InterruptedException { boolean ret = terminationLatch.await(millis, TimeUnit.MILLISECONDS); if (!ret) { System.out.println(terminationLatch.getCount()); } return ret; } } }