/* * Galaxy * Copyright (C) 2012 Parallel Universe Software Co. * * This file is part of Galaxy. * * Galaxy is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Galaxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Galaxy. If not, see <http://www.gnu.org/licenses/>. */ package co.paralleluniverse.galaxy.core; import co.paralleluniverse.galaxy.CacheListener; import co.paralleluniverse.common.io.Persistable; import co.paralleluniverse.galaxy.AbstractCacheListener; import co.paralleluniverse.galaxy.LineFunction; import co.paralleluniverse.galaxy.RefNotFoundException; import co.paralleluniverse.galaxy.TimeoutException; import co.paralleluniverse.galaxy.cluster.NodeInfo; import com.google.common.base.Charsets; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import static org.junit.Assume.*; import org.hamcrest.Matcher; import static org.hamcrest.CoreMatchers.*; import static org.junit.matchers.JUnitMatchers.*; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mockito; import static org.mockito.Mockito.*; import static org.mockito.Matchers.*; import static org.mockito.Matchers.any; import static co.paralleluniverse.galaxy.test.LogMock.startLogging; import static co.paralleluniverse.galaxy.test.LogMock.stopLogging; import static co.paralleluniverse.galaxy.test.LogMock.when; import static co.paralleluniverse.galaxy.test.LogMock.doAnswer; import static co.paralleluniverse.galaxy.test.LogMock.doNothing; import static co.paralleluniverse.galaxy.test.LogMock.doReturn; import static co.paralleluniverse.galaxy.test.LogMock.doThrow; import static co.paralleluniverse.galaxy.test.LogMock.mock; import static co.paralleluniverse.galaxy.test.LogMock.spy; import static co.paralleluniverse.galaxy.test.MockitoUtil.*; import static co.paralleluniverse.galaxy.core.MessageMatchers.*; import static co.paralleluniverse.galaxy.core.Cache.CacheLine; import static co.paralleluniverse.galaxy.core.Cache.State; import static co.paralleluniverse.galaxy.core.Cache.State.*; import static co.paralleluniverse.galaxy.core.Cache.PENDING; import co.paralleluniverse.galaxy.core.RefAllocator.RefAllocationsListener; import co.paralleluniverse.galaxy.core.Message.BACKUP; import co.paralleluniverse.galaxy.core.Message.LineMessage; import co.paralleluniverse.galaxy.core.Message.MSG; import co.paralleluniverse.galaxy.core.Message.Type; import static co.paralleluniverse.galaxy.core.Op.Type.*; import com.google.common.util.concurrent.ListenableFuture; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import org.junit.Ignore; import org.junit.Rule; import org.junit.rules.TestName; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.internal.util.MockUtil; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** * * @author pron */ @RunWith(Parameterized.class) public class CacheTest { private static final int DEFAULT_ALLOC_COUNT = 10000; Cache cache; FullCluster cluster; AbstractComm comm; Backup backup; CacheStorage storage; CacheMonitor monitor; boolean hasServer; long messageId = 0; // public CacheTest() { // this.hasServer = true; // } public CacheTest(boolean hasServer) { this.hasServer = hasServer; } @Parameterized.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {true}, {false},}); } @Before public void setUp() throws Exception { java.util.logging.Logger.getLogger("").setLevel(java.util.logging.Level.WARNING); cluster = mock(FullCluster.class); when(cluster.isMaster()).thenReturn(true); when(cluster.hasServer()).thenReturn(hasServer); when(cluster.getMyNodeId()).thenReturn(sh(5)); comm = mock(AbstractComm.class); backup = mock(Backup.class); when(backup.inv(anyLong(), anyShort())).thenReturn(true); storage = spy(new HeapLocalStorage("test", null)); monitor = mock(CacheMonitor.class); cache = makeCache(10000); cache.setReceiver(mock(MessageReceiver.class)); messageId = 0; } Cache makeCache(int capacity) throws Exception { return makeCache(capacity, false); } Cache makeCache(boolean syncrhonous) throws Exception { return makeCache(1000, syncrhonous); } Cache makeCache(int capacity, boolean syncrhonous) throws Exception { Cache _cache = new Cache("test", cluster, comm, storage, backup, monitor, capacity); _cache.setReuseLines(false); _cache.setReuseSharerSets(false); _cache.setSynchronous(syncrhonous); _cache.init(); // verify uninteresting interactions so that test can use verifyNoMoreInteractions(). verify(monitor).setMonitoredObject(_cache); verify(comm).setReceiver(_cache); if (hasServer) verify(comm, atLeastOnce()).isSendToServerInsteadOfMulticast(); return _cache; } private void reset() throws Exception { setUp(); } @After public void tearDown() { } /////////////////////////////////////////////////////////////////////// /** * A get returns data after PUT has been received. */ @Test public void whenPUTThenGetLine() throws Exception { PUT(1, sh(10), 1, "hello"); byte[] res = (byte[]) doOp(GET, 1L); assertState(1, S, null); assertVersion(1, 1); assertThat(deserialize(res), is("hello")); verify(monitor).addMessageReceived(Type.PUT); verify(monitor).addOp(eq(GET), anyLong()); verify(monitor).addHit(); verify(monitor).addStalePurge(0); verifyNoMoreInteractions(monitor); } /** * A getx returns data after PUTX has been received. */ @Test public void whenPUTXThenGetXLine() throws Exception { PUTX(1, sh(10), 1, "hello"); byte[] res = (byte[]) doOp(GETX, 1L); assertState(1, E, null); assertVersion(1, 1); assertThat(deserialize(res), is("hello")); verify(monitor).addMessageReceived(Type.PUTX); if (hasServer()) verify(monitor).addMessageReceived(Type.INVACK); verify(monitor).addOp(eq(GETX), anyLong()); if (hasServer) verify(monitor).addMessageSent(Type.INV); verify(monitor).addHit(); } /** * A GET/X message is broadcast when get/x a missing line. */ @Test public void whenGetMissingLineThenBroadcastGET() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { Object res = cache.runOp(new Op(getType, 1L, null)); assertThat(res, is(PENDING)); assertState(1, I, getType == GETX ? O : S); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1L)))); verify(monitor).addMessageSent(matchingMessage(getType, sh(-1), 1L).getType()); verify(monitor).addMiss(); verifyNoMoreInteractions(monitor); reset(); } } /** * A GETX message is broadcast when getting, and getting X a missing line at the same time. */ @Test public void whenGetXAndGetMissingLineThenBroadcastGETX() throws Exception { Object res = cache.runOp(new Op(GETX, 1L, null)); assertThat(res, is(PENDING)); res = cache.runOp(new Op(GET, 1L, null)); assertThat(res, is(PENDING)); assertState(1, I, O); assertVersion(1, 0); verify(comm).send(argThat(equalTo(Message.GETX(sh(-1), 1L)))); verify(comm, never()).send(argThat(equalTo(Message.GET(sh(-1), 1L)))); } /** * a getx of a shared line causes a GETX message is sent to owner. */ @Test public void whenPUTAndGetXThenGETX() throws Exception { PUT(1, sh(10), 1, "hello"); Object res = cache.runOp(new Op(GETX, 1L, null)); assertThat(res, is(PENDING)); assertState(1, S, O); assertVersion(1, 1); verify(comm).send(argThat(equalTo(Message.GETX(sh(10), 1L)))); } /** * get/x + hint results in GET/X sent only to hinted line. */ @Test public void whenGetWithHintThenGETFromHint() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { Object res = cache.runOp(new Op(getType, 1L, sh(15), null)); assertThat(res, is(PENDING)); assertState(1, I, getType == GETX ? O : S); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(15), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1L)))); reset(); } } /** * get/x + hint of a shared line results in GET/X sent only to owner (and not hinted line). */ @Test public void whenGetWithHintAndKnownOwnerThenGETFromOwner() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { PUT(1, sh(10), 1, "hello"); makeInvalid(sh(10), 1L); cache.runOp(new Op(getType, 1L, sh(15), null)); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(10), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(15), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1L)))); reset(); } } /** * get/x from owner results in GET/X sent only owner of the requested line line. */ @Test public void whenGetFromOwnerThenGETFromOwner() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { PUT(2, sh(10), 1, "hello"); cache.runOp(new Op(GET_FROM_OWNER, 2, new Op(getType, 1, null), null)); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(10), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1L)))); reset(); } } /** * get/x from owner broadcasts GET/X if owner line does not exist. */ @Test public void whenGetFromOwnerAndNoLineThenBroadcastGET() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { Op get = (Op) cache.runOp(new Op(GET_FROM_OWNER, 2, new Op(getType, 1, null), null)); cache.runOp(get); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(10), 1L)))); reset(); } } /** * get/x from owner sends GET/X to the actual owner of the requested line if it's known, not to the owner of the "owned" line. */ @Test public void whenGetFromOwnerAndKnownLineThenGETFromActualOwner() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { PUT(2, sh(10), 1, "hello"); PUT(1, sh(15), 1, "hello"); makeInvalid(sh(15), 1); cache.runOp(new Op(GET_FROM_OWNER, 2, new Op(getType, 1, null), null)); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(15), 1L)))); verify(comm, never()).send(argThat(equalTo(matchingMessage(getType, sh(10), 1L)))); reset(); } } /** * When GET is received then PUT is sent */ @Test public void whenGETThenPUT() throws Exception { PUTX(1234L, sh(10), 2, "hello", 20, 30, 40); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, O, null); final LineMessage get1 = Message.GET(sh(50), 1234L); final LineMessage get2 = Message.GET(sh(60), 1234L); cache.receive(get1); cache.receive(get2); assertState(1234L, O, null); verify(comm).send(argThat(equalTo(Message.PUT(get1, 1234L, 2, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUT(get2, 1234L, 2, toBuffer("hello"))))); } public static LineFunction<Long> storefunc(final long set) { return new LineFunction<Long>() { @Override public Long invoke(LineAccess lineAccess) { try { ByteBuffer get = lineAccess.getForRead(); if (!Charset.forName("ISO-8859-1").newDecoder().decode(get).toString().equals("hello")) return 0L; ByteBuffer bb = lineAccess.getForWrite(8); bb.clear(); bb.putLong(set); bb.flip(); return set; } catch (CharacterCodingException ex) { throw new AssertionError(ex); } } }; } // @Test public void whenInvokeLocalAndOThenInvokeAndSendINVAndBecomeE() throws Exception { PUTX(1234L, sh(10), 2, "hello", 20); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, O, null); long l = (Long) cache.doOp(Op.Type.INVOKE, 1234L, storefunc(45L), null, null); assertModified(1234L, true); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1234L, sh(10))))); assertState(1234L, O, E); assertThat(l, equalTo(45L)); } @Test public void testInvokeNotOwnerInI() throws Exception { setCommMsgCounter(); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); final LineFunction<Long> storefunc = storefunc(45L); ListenableFuture<Object> future = cache.doOpAsync(Op.Type.INVOKE, 1234L, storefunc, null, null); final Message.INVOKE msg = Message.INVOKE(sh(-1), 1234L, storefunc); verify(comm).send(argThat(equalTo(msg.setMessageId(1)))); assertState(1234L, I, null); cache.receive(Message.INVRES(msg, 1234L, 45L)); assertThat((long) future.get(), equalTo(45L)); } @Test public void testInvokeNotOwnerInS() throws Exception { setCommMsgCounter(); PUT(1234L, sh(10), 2, "hello"); final LineFunction<Long> storefunc = storefunc(45L); assertState(1234L, S, null); ListenableFuture<Object> future = cache.doOpAsync(Op.Type.INVOKE, 1234L, storefunc, null, null); final Message.INVOKE msg = Message.INVOKE(sh(10), 1234L, storefunc); verify(comm).send(argThat(equalTo(msg.setMessageId(2)))); assertState(1234L, S, null); cache.receive(Message.INVRES(msg, 1234L, 45L)); assertThat((long) future.get(), equalTo(45L)); } @Test public void testInvokeWhenServerIsOwner() throws Exception { setCommMsgCounter(); PUT(1234L, sh(10), 2, "hello"); final LineFunction<Long> storefunc = storefunc(45L); assertState(1234L, S, null); ListenableFuture<Object> future = cache.doOpAsync(Op.Type.INVOKE, 1234L, storefunc, null, null); final Message msg = Message.INVOKE(sh(10), 1234L, storefunc).setMessageId(2); verify(comm).send(argThat(equalTo(msg))); assertState(1234L, S, null); // Server reply with putx PUTX(1234L, sh(10), 2, "hello"); assertState(1234L, E, null); assertThat((long) future.get(), equalTo(45L)); } @Test public void testInvokeLocalWhenOwnerIsE() throws Exception { setCommMsgCounter(); PUTX(1234L, sh(10), 2, "hello"); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, E, null); long l = (Long) cache.doOp(Op.Type.INVOKE, 1234L, storefunc(45L), null, null); assertState(1234L, E, null); assertThat(l, equalTo(45L)); } @Test public void testHandleInvokeWhenE() throws Exception { PUTX(1234L, sh(10), 2, "hello"); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, E, null); final LineFunction<Long> storefunc = storefunc(45L); final Message.INVOKE msg = Message.INVOKE(sh(10), 1234L, storefunc); cache.receive(msg); verify(comm).send(argThat(equalTo(Message.INVRES(msg, 1234L, 45L)))); assertModified(1234L, true); } @Ignore // not true since c62a579 when the optimization in transitionToE has been removed @Test public void testHandleInvokeWhenO() throws Exception { PUTX(1234L, sh(10), 2, "hello", 20); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, O, null); final LineFunction<Long> storefunc = storefunc(45L); final Message.INVOKE msg = Message.INVOKE(sh(10), 1234L, storefunc); cache.receive(msg); assertModified(1234L, true); assertState(1234L, O, E); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1234L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INVRES(msg, 1234L, 45L)))); } /** * When GETX is received then PUTX is sent */ @Test public void whenGETXThenPUTX() throws Exception { PUTX(1234L, sh(10), 2, "hello", 20, 30, 40); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))); assertState(1234L, O, null); final LineMessage get1 = Message.GET(sh(50), 1234L); final LineMessage get2 = Message.GET(sh(60), 1234L); cache.receive(get1); cache.receive(get2); assertState(1234L, O, null); verify(comm).send(argThat(equalTo(Message.PUT(get1, 1234L, 2, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUT(get2, 1234L, 2, toBuffer("hello"))))); final LineMessage getx = Message.GETX(sh(50), 1234L); cache.receive(getx); assertOwner(1234L, sh(50)); assertState(1234L, I, null); verify(comm).send(argThat(equalTo(Message.PUTX(getx, 1234L, sh(20, 30, 40, 50, 60), 0, 2, toBuffer("hello"))))); } /** * When GET/X is received and not broadcast and line is not found, send CHNGD_OWNER */ @Test public void whenGETAndNoLineThenCHNGD_OWNER() throws Exception { for (Message.Type getType : new Message.Type[]{Message.Type.GET, Message.Type.GETX}) { final LineMessage get = new Message.GET(getType, sh(50), 1234L); cache.receive(get); verify(comm).send(argThat(equalTo(Message.CHNGD_OWNR(get, 1234L, sh(-1), false)))); reset(); } } /** * When GET/X broadcast is received and line is not found, send ACK */ @Test public void whenGETBcastAndNoLineThenACK() throws Exception { for (Message.Type getType : new Message.Type[]{Message.Type.GET, Message.Type.GETX}) { final LineMessage get = new Message.GET(getType, sh(-1), 1234L); cache.receive(get); verify(comm).send(argThat(equalTo(Message.ACK(get)))); reset(); } } /** * When CHNGD_OWNR is received as a response to a GET, then re-send GET to the new owner. */ @Test public void whenGetAndCHNGD_OWNRThenGETAgain() throws Exception { cache.runOp(new Op(GET, 1234L, null)); when(cluster.getMaster(sh(20))).thenReturn(makeNodeInfo(sh(20))); LineMessage msg = (LineMessage) captureMessage(); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(20), true)); verify(comm).send(argThat(equalTo(Message.GET(sh(20), 1234L)))); } /** * When CHNGD_OWNR is received as a response to a GET, but new owner is not in cluster then re-send GET to the old owner. */ @Test public void whenGetAndCHNGD_OWNRAndNoNodeThenGETAgainIgnoreChange() throws Exception { PUT(1234L, sh(10), 1, "x"); INV(1234L, sh(10)); Mockito.reset(comm); // to enable capture cache.runOp(new Op(GET, 1234L, null)); when(cluster.getMaster(sh(20))).thenReturn(null); LineMessage msg = (LineMessage) captureMessage(); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(20), true)); verify(comm).send(argThat(equalTo(Message.GET(sh(10), 1234L)))); } /** * When CHNGD_OWNR is received as a response to a GETX, then re-send GETX to the new owner. */ @Test public void whenGetxAndCHNGD_OWNRThenGETXAgain() throws Exception { PUT(1234L, sh(10), 1L, "foo"); when(cluster.getMaster(sh(20))).thenReturn(makeNodeInfo(sh(20))); cache.runOp(new Op(GETX, 1234L, null)); LineMessage msg = (LineMessage) captureMessage(); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(20), true)); verify(comm).send(argThat(equalTo(Message.GETX(sh(20), 1234L)))); } @Test public void whenGetxAndCHNGD_OWNRToYou() throws Exception { ListenableFuture<Object> future = cache.doOpAsync(GETX, 1234L, null, null, null); LineMessage msg = (LineMessage) captureMessage(); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(10), true)); assertThat(future.isDone(), is(false)); cache.receive(Message.PUTX(msg, 1234L, null, 0, 1L, toBuffer("foo"))); if (hasServer()) INVACK(1234L, sh(0)); assertThat(future.isDone(), is(true)); assertThat(deserialize(future.get()), equalTo("foo")); } @Test public void whenSendToOwnerOfAndCHNGD_OWNRToYou() throws Exception { /** * When CHNGD_OWNR is received, resend message to new owner */ setCommMsgCounter(); PUT(1234L, sh(10), 1L, "xxx"); INV(1234L, sh(10)); verify(comm).send(argThat(equalTo(Message.INVACK(Message.INV(sh(10), 1234L, sh(-1))).setMessageId(2)))); MSG msg = Message.MSG(sh(-1), 1234L, true, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.MSG(sh(10), 1234L, true, serialize("foo")).setMessageId(3)))); assertThat(send.getFuture().isDone(), is(false)); //when(cluster.getMaster(sh(20))).thenReturn(makeNodeInfo(sh(20))); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(10), true)); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(10), true)); // twice, but only resend once cache.receive(Message.TIMEOUT(msg)); cache.receive(Message.PUTX(msg, 1234L, null, 0, 1L, toBuffer("xxx"))); assertThat(send.getFuture().isDone(), is(true)); try { send.getResult(); fail("TimeoutException not thrown"); } catch (InterruptedException | ExecutionException e) { assertThat(e.getCause(), is(instanceOf(TimeoutException.class))); } } /** * When INV is received as a response from server to an INV, then re-send GETX to the new owner. */ @Test public void whenGetxAndINVFromServerThenGETXAgain() throws Exception { assumeTrue(hasServer); when(comm.isSendToServerInsteadOfMulticast()).thenReturn(true); cache = makeCache(10000); cache.runOp(new Op(GETX, 1234L, null)); verify(comm).send(argThat(equalTo(Message.GETX(sh(-1), 1234L)))); PUTX(1234L, sh(10), 1L, "foo", 20, 30); verify(comm).send(argThat(equalTo(Message.INV(sh(0), 1234L, sh(10))))); cache.receive(Message.INV(Message.INV(sh(0), 1234L, sh(10)), 1234, sh(20))); verify(comm).send(argThat(equalTo(Message.GETX(sh(20), 1234L)))); verify(comm, never()).send(argThat(equalTo(Message.INVACK(Message.INV(sh(0), 1234L, sh(10)))))); } /** * When NOT_FOUND is received (from peers) as a response to a GET, get from server. */ @Test public void whenGetAndNOT_FOUNDAndHasServerThenGETFromServer() throws Exception { assumeTrue(hasServer); cache.runOp(new Op(GET, id(1234L), null)); verify(comm).send(argThat(equalTo(Message.GET(sh(-1), id(1234))))); cache.receive(Message.NOT_FOUND(Message.GET(sh(-1), id(1234)))); verify(comm).send(argThat(equalTo(Message.GET(sh(0), id(1234))))); } /** * When NOT_FOUND is received and no server, then throw exception. */ @Test public void whenGetAndNOT_FOUNDAndNotHasServerThenThrowException() throws Exception { assumeTrue(!hasServer); final Op get = new Op(GET, id(1234), null); cache.runOp(get); cache.receive(Message.NOT_FOUND(Message.GET(sh(-1), id(1234)))); try { get.getResult(); fail("exception not thrown"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(RefNotFoundException.class))); } } /** * When NOT_FOUND is received from server then throw exception. */ @Test public void whenGetAndNOT_FOUNDFromServerThenThrowException() throws Exception { assumeTrue(hasServer); final Op get = new Op(GET, id(1234), null); cache.runOp(get); cache.receive(Message.NOT_FOUND(Message.GET(sh(0), id(1234)))); try { get.getResult(); fail("exception not thrown"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(RefNotFoundException.class))); } } /** * When MSG is received and not broadcast and line is not found, send CHNGD_OWNER */ @Test public void whenMSGAndNoLineThenCHNGD_OWNER() throws Exception { final LineMessage msg = Message.MSG(sh(10), 1234L, true, serialize("foo")); cache.receive(msg); verify(comm).send(argThat(equalTo(Message.CHNGD_OWNR(msg, 1234L, sh(-1), false)))); } /** * When MSG broadcast is received and line is not found, send ACK */ @Test public void whenMSGBcastAndNoLineThenACK() throws Exception { final LineMessage msg = Message.MSG(sh(-1), 1234L, true, serialize("foo")); cache.receive(msg); verify(comm).send(argThat(equalTo(Message.ACK(msg)))); } /** * When GET/X is received and line is not owner, send CHNGD_OWNER */ @Test public void whenGETAndNotOwnerThenCHNGD_OWNER() throws Exception { for (Message.Type getType : new Message.Type[]{Message.Type.GET, Message.Type.GETX}) { PUT(1234L, sh(10), 1L, "hello"); final LineMessage get = new Message.GET(getType, sh(50), 1234L); cache.receive(get); verify(comm).send(argThat(equalTo(Message.CHNGD_OWNR(get, 1234L, sh(10), true)))); reset(); } } /** * When GET/X broadcast is received and line is not owner but owner is known, send CHNGD_OWNER */ @Test public void whenGETBcastAndKnownOwnerThenCHNGD_OWNER() throws Exception { for (Message.Type getType : new Message.Type[]{Message.Type.GET, Message.Type.GETX}) { PUT(1234L, sh(10), 1L, "hello"); final LineMessage get = new Message.GET(getType, sh(-1), 1234L); cache.receive(get); verify(comm).send(argThat(equalTo(Message.CHNGD_OWNR(get, 1234L, sh(10), true)))); reset(); } } /** * When GET/X broadcast is received and line owner is unknown, send ACK */ @Test public void whenGETBcastAndUnknownOwnerThenACK() throws Exception { for (Message.Type getType : new Message.Type[]{Message.Type.GET, Message.Type.GETX}) { cache.runOp(new Op(GET, 1234L, null)); // create an I line with unknown owner final LineMessage get = new Message.GET(getType, sh(-1), 1234L); cache.receive(get); verify(comm).send(argThat(equalTo(Message.ACK(get)))); reset(); } } /** * When INV is received and line does not exist INVACK */ @Test public void whenINVAndNoLineThenINVACK() throws Exception { Message.INV inv = Message.INV(sh(10), 1234L, sh(10)); cache.receive(inv); verify(comm).send(argThat(equalTo(Message.INVACK(inv)))); } /** * When GETX is receive, inv slaves before responding with PUTX */ @Ignore @Test public void whenGetXAndHasSlavesThenWaitForSlavesBeforePUTX() throws Exception { when(backup.inv(anyLong(), anyShort())).thenReturn(false); PUTX(1234L, sh(10), 1L, "hello"); set(1234L, "bye"); cache.receive(Message.BACKUPACK(sh(0), 1234L, 2L)); assertState(1234L, E, null); assertThat(cache.getLine(1234L).is(CacheLine.SLAVE), is(true)); Message.GET getx = Message.GETX(sh(10), 1234L); cache.receive(getx); verify(backup).inv(1234L, sh(10)); verify(comm, never()).send(argThat(equalTo(Message.PUTX(getx, 1234L, new short[0], 0, 2L, toBuffer("bye"))))); cache.receive(Message.INVACK(sh(5), 1234L).setIncoming()); assertThat(cache.getLine(1234L).is(CacheLine.SLAVE), is(false)); verify(comm).send(argThat(equalTo(Message.PUTX(getx, 1234L, new short[0], 0, 2L, toBuffer("bye"))))); } /** * When INV is receive, inv slaves before responding with INVACK */ // @Test // public void whenInvAndHasSlavesThenWaitForSlavesBeforeINVACK() throws Exception { // when(backup.inv(anyLong(), anyShort())).thenReturn(false); // // PUTX(1234L, sh(10), 1L, "hello"); // // set(1234L, "bye"); // cache.receive(Message.BACKUPACK(sh(0), 1234L, 2L)); // cache.receive(Message.GET(sh(20), 1234L)); // // assertState(1234L, S, null); // assertThat(cache.getLine(1234L).is(CacheLine.SLAVE), is(true)); // // Message.INV inv = Message.INV(sh(10), 1234L, sh(10)); // cache.receive(inv); // // verify(backup).inv(1234L, sh(10)); // verify(comm, never()).send(argThat(equalTo(Message.INVACK(inv)))); // // cache.receive(Message.INVACK(sh(5), 1234L).setIncoming()); // // assertThat(cache.getLine(1234L).is(CacheLine.SLAVE), is(false)); // verify(comm).send(argThat(equalTo(Message.INVACK(inv)))); // } /** * A put allocates ID and line */ @Test public void whenPutThenAllocateId() throws Exception { final RefAllocationsListener listener = hasServer ? null : getRefAllocationListener(cache.getRefAllocator()); Object res; Op put; put = new Op(PUT, -1L, serialize("1111"), null); res = cache.runOp(put); assertThat(res, is(PENDING)); if (hasServer) verify(comm).send(argThat(equalTo(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT)))); else verify(cluster).allocateRefs(anyInt()); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 100, 3)); else listener.refsAllocated(100, 3); res = put.getResult(); assertThat((Long) res, is(100L)); res = get(100L); assertThat((String) res, is("1111")); assertModified(100L, true); assertThat(cache.getLine(100L).isLocked(), is(true)); res = doOp(PUT, -1L, serialize("2222")); assertThat((Long) res, is(101L)); assertModified(101L, true); assertThat(cache.getLine(101L).isLocked(), is(true)); res = get(101L); assertThat((String) res, is("2222")); if (hasServer) verify(comm, times(2)).send(argThat(equalTo(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT)))); else verify(cluster, times(2)).allocateRefs(anyInt()); // allocates after id > (min + max) / 2 res = doOp(PUT, -1L, serialize("3333")); assertThat((Long) res, is(102L)); assertModified(102L, true); assertThat(cache.getLine(102L).isLocked(), is(true)); res = get(102); assertThat((String) res, is("3333")); put = new Op(PUT, -1L, serialize("4444"), null); res = cache.runOp(put); assertThat(res, is(PENDING)); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 200, 3)); else listener.refsAllocated(200, 3); res = put.getResult(); assertThat((Long) res, is(200L)); res = doOp(GET, 200L); assertThat(deserialize((byte[]) res), is("4444")); assertModified(200L, true); assertThat(cache.getLine(200L).isLocked(), is(true)); res = doOp(PUT, -1L, serialize("5555")); assertThat((Long) res, is(201L)); assertModified(201L, true); assertThat(cache.getLine(201L).isLocked(), is(true)); res = get(201L); assertThat((String) res, is("5555")); if (hasServer) verify(comm, times(3)).send(argThat(equalTo(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT)))); else verify(cluster, times(3)).allocateRefs(anyInt()); // allocates after id > (min + max) / 2 res = doOp(PUT, -1L, serialize("6666")); assertThat((Long) res, is(202L)); assertModified(202L, true); assertThat(cache.getLine(202L).isLocked(), is(true)); res = get(202L); assertThat((String) res, is("6666")); } /** * When an owned line is getx, INV all sharers, including server if hasServer(), but if we have a server, we can get and even * set. We don't wait for ACKs to complete. */ @Test public void whenPUTXAndGetXThenINV() throws Exception { PUTX(1, sh(10), 1, "hello", 20, 30); Object res = cache.runOp(new Op(GETX, 1L, null)); assertState(1, O, E); assertVersion(1, 1); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(Comm.SERVER, 1L, sh(10))))); // assertThat(deserialize((byte[]) res), is("hello")); // not true since c62a579 when the optimization in transitionToE has been removed } /** * When an owned line is getx, INV all sharers, including server if hasServer(), but we don't wait for ACKs to complete if * hasServer. */ @Test @Ignore // not true since c62a579 when the optimization in transitionToE has been removed public void whenGetxAndPUTXThenDontWaitForINVACKIffServer() throws Exception { Op getx = new Op(GETX, 1L, null); Object res = cache.runOp(getx); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.GETX(sh(-1), 1L)))); if (hasServer) PUTX(1L, sh(10), 1L, "hello", 20, 30); else PUTX(1L, sh(10), 1L, "hello", 10, 20, 30); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(Comm.SERVER, 1L, sh(10))))); else verify(comm).send(argThat(equalTo(Message.INV(sh(10), 1L, sh(10))))); if (hasServer) { assertThat(getx.getFuture().isDone(), is(true)); assertThat(deserialize((byte[]) getx.getResult()), is("hello")); } else { assertThat(getx.getFuture().isDone(), is(false)); } } /** * When an owned line is getx and isSendToServerInsteadOfMulticast then wait for an INVACK from the server. */ @Test public void whenGetxAndPUTXAndSendToServerThenDoWaitForINVACKFromServer() throws Exception { assumeTrue(hasServer); when(comm.isSendToServerInsteadOfMulticast()).thenReturn(true); cache = makeCache(10000); Op getx = new Op(GETX, 1L, null); Object res = cache.runOp(getx); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.GETX(sh(-1), 1L)))); PUTX(1L, sh(10), 1L, "hello", 20, 30); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(Comm.SERVER, 1L, sh(10))))); assertThat(getx.getFuture().isDone(), is(false)); // - since c62a579 cache.receive(Message.INVACK(Message.INV(sh(20), 1L, sh(10)))); cache.receive(Message.INVACK(Message.INV(sh(30), 1L, sh(10)))); assertThat(getx.getFuture().isDone(), is(false)); // - cache.receive(Message.INVACK(Message.INV(Comm.SERVER, 1L, sh(10)))); assertThat(getx.getFuture().isDone(), is(true)); assertThat(deserialize((byte[]) getx.getResult()), is("hello")); } /** * When an owned line is getx and no server then wait for an INVACK from the previous owner (means that it inved its slaves). */ @Test @Ignore // not true since c62a579 public void whenGetxAndPUTXAndNoServerThenDoWaitForINVACKFromPreviousOwner() throws Exception { assumeTrue(!hasServer); Op getx = new Op(GETX, 1L, null); Object res = cache.runOp(getx); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.GETX(sh(-1), 1L)))); PUTX(1L, sh(10), 1L, "hello", 10, 20, 30); verify(comm).send(argThat(equalTo(Message.INV(sh(10), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); assertThat(getx.getFuture().isDone(), is(false)); cache.receive(Message.INVACK(sh(10), 1L)); assertThat(getx.getFuture().isDone(), is(true)); assertThat(deserialize((byte[]) getx.getResult()), is("hello")); } /** * When a line is S and we getx, the current node will be one of the sharers. It must not try to INV itself. */ @Test public void whenSAndGetxDontINVSelf() throws Exception { PUT(1L, sh(10), 1, "hello"); PUTX(1L, sh(10), 1, "hello", 5, 20); cache.runOp(new Op(GETX, 1L, null)); assertState(1, O, E); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); } /** * Don't respond to GET message if all INVACKs have not yet been received. */ @Test public void whenPUTXAndGetXThenDontRespondToRequests() throws Exception { PUTX(1, sh(10), 1, "hello", 20, 30); Object res = cache.runOp(new Op(GETX, 1L, null)); // if (hasServer()) { // assertThat(res, is(not(PENDING))); // cache.unlockLine(cache.getLine(1L), null); // } cache.receive(Message.GET(sh(100), 1)); assertState(1, O, E); assertVersion(1, 1); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(Comm.SERVER, 1L, sh(10))))); verify(comm, never()).send(argThat(equalTo(Message.PUT(sh(100), 1L, 1L, null)))); } /** * Once all INVACKs have been received, respond to GET and set state from E to O. */ @Test @Ignore // - not true since c62a579 public void whenPUTXAndGetXAndAcksAndRequestsThenRespond() throws Exception { PUTX(1, sh(10), 1, "hello", 20, 30); Op getx = new Op(GETX, 1L, null); Object res = cache.runOp(getx); assertThat(res, is(not(PENDING))); // - not true since c62a579 cache.unlockLine(cache.getLine(1L), null); cache.receive(Message.GET(sh(100), 1)); cache.receive(Message.INVACK(Message.INV(sh(20), 1L, sh(10)))); cache.receive(Message.INVACK(Message.INV(sh(30), 1L, sh(10)))); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1L, sh(10)))); cache.receive(Message.BACKUPACK(sh(0), 1L, 1L)); assertState(1, O, null); // after c62a579 this would be O -> E b/c op GETX is still pending assertVersion(1, 1); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 1L, sh(10))))); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), 1), 1L, 1L, toBuffer("hello"))))); assertThat(deserialize(res), is("hello")); } /** * When PUT is received, the get op is completed */ @Test public void whenPUTThenCompleteGet() throws Exception { final Op get = new Op(GET, 1L, null); Object res = cache.runOp(get); assertThat(res, is(PENDING)); assertState(1, I, S); PUT(1, sh(10), 1, "hello"); assertState(1, S, null); assertThat(deserialize(get.getResult()), is("hello")); } /** * When PUTX is received, the getx op is completed */ // @Test public void whenPUTXThenCompleteGetx() throws Exception { final Op getx = new Op(GETX, 1L, null); Object res = cache.runOp(getx); assertThat(res, is(PENDING)); assertState(1, I, O); PUTX(1, sh(10), 1, "hello", 20, 30); assertState(1, O, E); if (hasServer) assertThat(deserialize(getx.getResult()), is("hello")); cache.receive(Message.INVACK(Message.INV(sh(20), 1L, sh(10)))); cache.receive(Message.INVACK(Message.INV(sh(30), 1L, sh(10)))); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), 1L, sh(10)))); assertThat(deserialize(getx.getResult()), is("hello")); assertState(1, E, null); assertOwner(1, sh(5)); } /** * Tests execution of pending messages on a locked line. */ @Test public void testPendingMessages1() throws Exception { //public void whenLockedThenDontProecssMessagesUntilRelease() { for (Op.Type getType : new Op.Type[]{GETX, GETS}) { PUTX(1234L, sh(1), 1, "hello"); cache.runOp(new Op(getType, 1234L, null)); cache.receive(Message.GET(sh(10), 1234L)); cache.receive(Message.GET(sh(20), 1234L)); cache.receive(Message.GETX(sh(30), 1234L)); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(sh(0), 1234L, sh(1))))); verifyNoMoreInteractions(comm); cache.release(1234); cache.receive(Message.BACKUPACK(sh(0), 1234L, 1L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(10), 1234L), 1234L, 1L, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(20), 1234L), 1234L, 1L, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUTX(Message.GETX(sh(30), 1234L), 1234L, sh(10, 20), 0, 1L, toBuffer("hello"))))); reset(); } } /** * Tests execution of pending messages on a locked line. */ @Test public void testPendingMessages2() throws Exception { PUTX(1234L, sh(1), 1, "hello"); cache.runOp(new Op(GETX, 1234L, null)); cache.receive(Message.GET(sh(10), 1234L)); cache.receive(Message.GETX(sh(20), 1234L)); cache.receive(Message.GET(sh(30), 1234L)); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(sh(0), 1234L, sh(1))))); verifyNoMoreInteractions(comm); cache.release(1234); cache.receive(Message.BACKUPACK(sh(0), 1234L, 1L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(10), 1234L), 1234L, 1L, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUTX(Message.GETX(sh(20), 1234L), 1234L, new short[]{sh(10)}, 0, 1L, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.CHNGD_OWNR(Message.GET(sh(30), 1234L), 1234L, sh(20), false)))); // b/c hasServer: I, no-server: S } /** * When there are messages waiting (b/c of MODIFIED) and the line isn't locked, new ops will wait as well and let the messages * go first to prevent starvation. */ @Test public void testPendingOps() throws Exception { PUTX(1234L, sh(1), 1, "hello"); Mockito.reset(comm); // forget sends assertLocked(1234L, false); assertState(1234, E, null); set(1234L, "bye"); set(1234L, "1234"); // this length must be less than the sets' buffer length to make sure a new buffer is allocated to them // otherwise, if this is longer, the sets will write over the buffer. This is OK in practice, because send will have been finished // by then, but here, mockito is capturing pointers to the buffers and verifying them after the set. assertModified(1234, true); assertVersion(1234, 3); assertThat(get(1234L), is("1234")); GET(1234L, sh(10)); // now get is waiting verify(comm, never()).send(any(Message.class)); Object res; res = cache.runOp(new Op(SET, 1234L, serialize("why?????"), null)); // op is waiting because GET is waiting assertThat(res, is(PENDING)); res = cache.runOp(new Op(SET, 1234L, serialize("because!!!!!"), null)); // op is waiting because GET is waiting assertThat(res, is(PENDING)); cache.receive(Message.BACKUPACK(sh(0), 1234L, 3L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(10), 1234L), 1234L, 3L, toBuffer("1234"))))); // handling the GET verify(comm).send(argThat(equalTo(Message.INV(sh(10), 1234L, sh(5))))); // for the sets INVACK(1234L, sh(10)); // added b/c of c62a579 assertThat(get(1234L), is("because!!!!!")); assertModified(1234, true); assertVersion(1234, 5); // assertState(1234L, O, E); -- since c62a579 // INVACK(1234L, sh(10)); assertState(1234L, E, null); } /** * When there are messages waiting and the line isn't locked (b/c O -> E), new ops will execute */ // @Test public void testPendingOps2() throws Exception { PUTX(1234L, sh(1), 1, "hello", 20); if (hasServer()) { verify(comm).send(argThat(equalTo(Message.INV(sh(0), 1234L, sh(1))))); cache.receive(Message.INVACK(Message.INV(sh(0), 1234L, sh(1)))); } assertLocked(1234L, false); assertState(1234L, O, null); System.out.println("111: " + cache.getLine(1234L)); set(1234L, "bye"); set(1234L, "1234"); // this length must be less than the sets' buffer length to make sure a new buffer is allocated to them // otherwise, if this is longer, the sets will write over the buffer. This is OK in practice, because send will have been finished // by then, but here, mockito is capturing pointers to the buffers and verifying them after the set. assertState(1234L, O, E); assertModified(1234, true); assertVersion(1234, 3); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 1234L, sh(1))))); // we verify just for the sake of verifyNoMoreInteractions(comm) later assertThat(get(1234L), is("1234")); GET(1234L, sh(10)); // now get is waiting verifyNoMoreInteractions(comm); Object res; res = cache.runOp(new Op(SET, 1234L, serialize("why?????"), null)); // op is waiting because GET is waiting assertThat(res, is(not(PENDING))); res = cache.runOp(new Op(SET, 1234L, serialize("because!!!!!"), null)); // op is waiting because GET is waiting assertThat(res, is(not(PENDING))); cache.receive(Message.BACKUPACK(sh(0), 1234L, 5L)); verifyNoMoreInteractions(comm); cache.receive(Message.INVACK(Message.INV(sh(20), 1234L, sh(1)))); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(10), 1234L), 1234L, 5L, toBuffer("because!!!!!"))))); // handling the GET assertThat(get(1234L), is("because!!!!!")); assertModified(1234, false); assertVersion(1234, 5); assertState(1234L, O, null); } @Test public void whenPushThenPUTtoTarget() throws Exception { PUTX(1234L, sh(1), 1, "hello"); cache.runOp(new Op(PUSH, 1234L, sh(20, 30), null)); verify(comm).send(argThat(equalTo(Message.PUT(sh(20), 1234L, 1L, toBuffer("hello"))))); verify(comm).send(argThat(equalTo(Message.PUT(sh(30), 1234L, 1L, toBuffer("hello"))))); } @Test public void whenPushxThenPUTXtoTarget() throws Exception { PUTX(1234L, sh(1), 1, "hello"); cache.runOp(new Op(PUSHX, 1234L, sh(20), null)); cache.runOp(new Op(PUSHX, 1234L, sh(30), null)); verify(comm).send(argThat(equalTo(Message.PUTX(sh(20), 1234L, sh(), 0, 1L, toBuffer("hello"))))); verify(comm, never()).send(argThat(equalTo(Message.PUTX(sh(30), 1234L, sh(), 0, 1L, toBuffer("hello"))))); } @Test public void whenPushAndWrongStateThenIgnore() throws Exception { PUT(1234L, sh(1), 1, "hello"); cache.runOp(new Op(PUSH, 1234L, sh(20, 30), null)); cache.runOp(new Op(PUSH, 1111L, sh(10, 20), null)); cache.runOp(new Op(PUSHX, 1234L, sh(20), null)); cache.runOp(new Op(PUSHX, 1111L, sh(10), null)); verify(comm, never()).send(any(Message.class)); } /** * When line is M, allow local operations (get and set). */ @Test public void whenSetThenAllowLocalOps() throws Exception { PUTX(1234L, sh(1), 1, "hello"); assert !cache.getLine(1234).isLocked(); assertState(1234, E, null); set(1234L, "bye"); assertModified(1234, true); assertVersion(1234, 2); assertThat(get(1234L), is("bye")); // b/c line was not locked, set's autorelease issues a backup verify(backup).backup(1234, 2); set(1234L, "woohoo"); assertModified(1234, true); assertVersion(1234, 3); assertThat(get(1234L), is("woohoo")); verify(backup).backup(1234, 3); } /** * When line is M and and cache is in synchronous mode, don't allow local get until BACKUPACK */ @Ignore @Test public void whenSetSetAndSynchronousDontGetUntilBACKUPACK() throws Exception { cache = makeCache(true); PUTX(1234L, sh(1), 1, "hello"); assert !cache.getLine(1234).isLocked(); assertState(1234, E, null); set(1234L, "bye"); assertModified(1234, true); assertVersion(1234, 2); Op get = new Op(GET, 1234L, null); Object res = cache.runOp(get); assertThat(res, is(PENDING)); verify(backup).backup(1234, 2); // b/c line was not locked, set's autorelease issues a backup set(1234L, "woohoo"); verify(backup).backup(1234, 3); // b/c line was not locked, set's autorelease issues a backup assertModified(1234, true); assertVersion(1234, 3); cache.receive(Message.BACKUPACK(sh(0), 1234L, 3L)); assertThat(get(1234L), is("woohoo")); assertThat(get.getFuture().isDone(), is(true)); assertThat(deserialize((byte[]) get.getResult()), is("woohoo")); } /** * When updating a line, no messages can be processed until backup has completed. */ @Test public void whenSetThenDontProecssMessagesUntilBACKUPACK() throws Exception { PUTX(1234L, sh(1), 1, "hello"); assert !cache.getLine(1234).isLocked(); assertState(1234, E, null); set(1234L, "bye"); assertModified(1234, true); assertVersion(1234, 2); cache.receive(Message.GET(sh(100), 1234)); verify(backup).backup(1234, 2); verify(comm, never()).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), 1234L), 1234L, 2L, toBuffer("bye"))))); cache.receive(Message.BACKUPACK(sh(0), 1234L, 2L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), 1234L), 1234L, 2L, toBuffer("bye"))))); } /** * When line is M, hold push/x */ @Test public void whenSetThenDontPushUntilBACKUPACK() throws Exception { PUTX(1234L, sh(1), 1, "hello"); assertState(1234, E, null); set(1234L, "bye"); assert !cache.getLine(1234).isLocked(); assertModified(1234, true); assertVersion(1234, 2); cache.runOp(new Op(PUSH, 1234L, sh(10, 20), null)); GET(1234L, sh(100)); //cache.runOp(new Op(PUSHX, 1234L, sh(30))); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(sh(0), 1234L, sh(1))))); verifyNoMoreInteractions(comm); cache.receive(Message.BACKUPACK(sh(0), 1234L, 2L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), 1234L), 1234L, 2L, toBuffer("bye"))))); verify(comm).send(argThat(equalTo(Message.PUT(sh(10), 1234L, 2L, toBuffer("bye"))))); verify(comm).send(argThat(equalTo(Message.PUT(sh(20), 1234L, 2L, toBuffer("bye"))))); // By the time we get to pushx, we're not exclusive, so nothing will be sent verify(comm, never()).send(argThat(equalTo(Message.PUTX(sh(30), 1234L, hasServer() ? new short[]{sh(0)} : sh(), 0, 2L, toBuffer("bye"))))); } /** * When putting a new line, no messages can be processed until backup has completed. */ @Test public void whenPutThenDontProecssMessagesUntilBACKUPACK() throws Exception { long id = put("hello"); cache.release(id); assert !cache.getLine(id).isLocked(); assertState(id, E, null); assertModified(id, true); assertVersion(id, 1); cache.receive(Message.GET(sh(100), id)); verify(backup).backup(id, 1); verify(comm, never()).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), id), id, 1L, toBuffer("hello"))))); cache.receive(Message.BACKUPACK(sh(0), id, 1L)); verify(comm).send(argThat(equalTo(Message.PUT(Message.GET(sh(100), id), id, 1L, toBuffer("hello"))))); } /** * When multiple sets are issued, only backup when releasing the line. */ @Test public void onlyBackupUponEndTransaction() throws Exception { Transaction txn = cache.beginTransaction(); cache.runOp(new Op(GETX, 1234L, (Persistable) null, null, txn)); PUTX(1234L, sh(1), 1, "hello"); assert cache.getLine(1234).isLocked(); assertState(1234, E, null); set(1234L, "bye", txn); assertModified(1234, true); assertVersion(1234, 2); assertThat(get(1234L), is("bye")); // b/c line was locked, set does not autorelease and does not issue a backup verify(backup, never()).backup(anyLong(), anyLong()); set(1234L, "woohoo", txn); assertModified(1234, true); assertVersion(1234, 3); assertThat(get(1234L), is("woohoo")); cache.endTransaction(txn, false); verify(backup).backup(1234, 3); } /** * When we receive GET or GETX, we flush the backups */ @Test public void whenGETThenFlushBackups() { PUTX(1234L, sh(1), 1, "11"); cache.runOp(new Op(SET, 1234L, serialize("22"), null)); assertState(1234, E, null); assertModified(1234, true); verify(backup).backup(anyLong(), anyLong()); verify(backup, never()).flush(); cache.receive(Message.GET(sh(100), 1234)); verify(backup).flush(); } /** * Make sure we allow stale reads as long as there were no PUTs from the same owner. */ @Test public void testStaleReads1() { PUT(101L, sh(10), 1L, "1"); PUT(102L, sh(10), 1L, "2"); PUT(103L, sh(10), 1L, "3"); PUT(104L, sh(10), 1L, "4"); PUT(201L, sh(20), 1L, "1"); PUT(202L, sh(20), 1L, "2"); assertThat(cache.runOp(new Op(GET, 101, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 201, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 202, null)), is(not(PENDING))); INV(101L, sh(10)); INV(103L, sh(10)); INV(104L, sh(10)); INV(201L, sh(10)); INV(202L, sh(20)); assertThat(cache.runOp(new Op(GET, 101, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); PUT(103L, sh(10), 2L, "3"); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 101, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 201, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 202, null)), is(not(PENDING))); } /** * A PUT of a new line purges all I lines. */ @Test public void testStaleReads2() { PUT(101L, sh(10), 1L, "1"); PUT(102L, sh(10), 1L, "2"); PUT(103L, sh(10), 1L, "3"); PUT(104L, sh(10), 1L, "4"); PUT(201L, sh(20), 1L, "1"); PUT(202L, sh(20), 1L, "2"); assertThat(cache.runOp(new Op(GET, 101, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 201, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 202, null)), is(not(PENDING))); INV(101L, sh(10)); INV(103L, sh(10)); INV(104L, sh(10)); INV(201L, sh(20)); assertThat(cache.runOp(new Op(GET, 101, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); PUT(105L, sh(30), 1L, "5"); assertThat(cache.runOp(new Op(GET, 103, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 101, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 201, null)), is(PENDING)); assertThat(cache.runOp(new Op(GET, 202, null)), is(not(PENDING))); } /** * Eviction does not prevent stale reads. */ @Test public void testStaleReads4() { PUT(101L, sh(10), 1L, "1"); PUT(102L, sh(10), 1L, "2"); PUT(103L, sh(10), 1L, "3"); PUT(104L, sh(10), 1L, "4"); assertThat(cache.runOp(new Op(GET, 101, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 102, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 103, null)), is(not(PENDING))); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); evict(101, false); evict(102, false); PUT(103L, sh(10), 2L, "3"); INV(104L, sh(10)); assertThat(cache.runOp(new Op(GET, 104, null)), is(not(PENDING))); INV(103L, sh(10)); PUT(103L, sh(10), 3L, "3"); assertThat(cache.runOp(new Op(GET, 104, null)), is(PENDING)); } @Test public void testCacheListeners() { CacheListener listener = mock(CacheListener.class); cache.addCacheListener(listener); PUT(100L, sh(10), 1L, "hello"); verify(listener).received(cache, 100L, 1L, toBuffer("hello")); PUT(100L, sh(10), 2L, "bye"); verify(listener).received(cache, 100L, 2L, toBuffer("bye")); INV(100L, sh(10)); verify(listener).invalidated(cache, 100L); evict(100L, false); verify(listener).evicted(cache, 100L); } @Test public void testCacheLineListeners() throws Exception { CacheListener listener = mock(CacheListener.class); doOp(LSTN, 100L, listener); PUT(100L, sh(10), 1L, "hello"); verify(listener).received(cache, 100L, 1L, toBuffer("hello")); PUT(100L, sh(10), 2L, "bye"); verify(listener).received(cache, 100L, 2L, toBuffer("bye")); INV(100L, sh(10)); verify(listener).invalidated(cache, 100L); evict(100L, false); verify(listener).evicted(cache, 100L); PUT(100L, sh(20), 3L, "xxx"); verifyNoMoreInteractions(listener); // listener gone after eviction } /** * Local operations such as put and get do not notify listeners (but evict does)! */ @Test public void whenLocalInteractionThenDontNotifyListeners() throws Exception { CacheListener listener1 = mock(CacheListener.class); CacheListener listener2 = mock(CacheListener.class); cache.addCacheListener(listener1); long id = put("hello"); doOp(LSTN, id, listener2); set(id, "bye"); assert get(id).equals("bye"); set(id, "xxx"); assert get(id).equals("xxx"); del(id); //evict(id, false); verify(listener1).evicted(cache, id); verify(listener2).evicted(cache, id); verifyNoMoreInteractions(listener1); verifyNoMoreInteractions(listener2); } /** * A cache listener is able to set a line listener. */ @Test public void testSetLineListenerInCacheListener() throws Exception { final CacheListener lineListener = mock(CacheListener.class); cache.addCacheListener(new AbstractCacheListener() { @Override public void received(co.paralleluniverse.galaxy.Cache cache, long id, long version, ByteBuffer data) { try { doOp(LSTN, id, lineListener); } catch (TimeoutException e) { throw new AssertionError(e); } } }); PUT(100L, sh(10), 1L, "hello"); PUT(100L, sh(10), 2L, "bye"); verify(lineListener).received(cache, 100L, 2L, toBuffer("bye")); INV(100L, sh(10)); verify(lineListener).invalidated(cache, 100L); evict(100L, false); verify(lineListener).evicted(cache, 100L); PUT(100L, sh(20), 3L, "xxx"); verifyNoMoreInteractions(lineListener); // listener gone after eviction } @Test public void testDel() throws Exception { PUTX(1234L, sh(1), 1, "hello"); del(1234L); assertThat(cache.getLine(1234L).is(CacheLine.DELETED), is(true)); if (hasServer()) { assertLocked(1234L, false); verify(comm).send(argThat(equalTo(Message.DEL(Comm.SERVER, 1234L)))); cache.receive(Message.INVACK(Comm.SERVER, 1234L)); } assertState(1234L, I, null); } //@Test public void whenDeletedAndGETThenNOT_FOUND() { pending(); } @Test public void testSend() throws Exception { MSG msg = Message.MSG(sh(15), -1L, true, serialize("hello")); cache.send(msg); verify(comm).send(argThat(equalTo(msg))); } /** * When I'm the owner, message is immediately received (through shortCircuitReceive) */ @Test public void testSendToOwner1() { MessageReceiver receiver = mock(MessageReceiver.class); cache.setReceiver(receiver); PUTX(1234L, sh(1), 1, "hello"); cache.runOp(new Op(SEND, 1234, Message.MSG(sh(-1), 1234L, true, serialize("foo")), null)); verify(receiver).receive(argThat(equalTo(Message.MSG(sh(5), 1234L, true, serialize("foo"))))); } /** * When line not found then broadcast message */ @Test public void testSendToOwner2() throws Exception { setCommMsgCounter(); MSG msg = Message.MSG(sh(-1), 1234L, true, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.MSG(sh(-1), 1234L, true, serialize("foo")).setMessageId(1)))); assertThat(send.getFuture().isDone(), is(false)); cache.receive(Message.MSGACK(msg)); assertThat(send.getResult(), is(nullValue())); } /** * When INV is received, resend message to new owner */ @Test public void testSendToOwner3() throws Exception { setCommMsgCounter(); PUT(1234L, sh(10), 1L, "xxx"); MSG msg = Message.MSG(sh(-1), 1234L, true, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.MSG(sh(10), 1234L, true, serialize("foo")).setMessageId(2)))); assertThat(send.getFuture().isDone(), is(false)); INV(1234L, sh(20)); INV(1234L, sh(20)); // twice, but only resend once verify(comm).send(argThat(equalTo(Message.INVACK(Message.INV(sh(20), 1234L, sh(-1))).setMessageId(3)))); verify(comm).send(argThat(equalTo(Message.MSG(sh(20), 1234L, true, serialize("foo")).setMessageId(4)))); verify(comm).send(argThat(equalTo(Message.INVACK(Message.INV(sh(20), 1234L, sh(-1))).setMessageId(5)))); assertThat(send.getFuture().isDone(), is(false)); INV(1234L, sh(30)); INV(1234L, sh(30)); // twice, but only resend once verify(comm).send(argThat(equalTo(Message.INVACK(Message.INV(sh(30), 1234L, sh(-1))).setMessageId(6)))); verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, true, serialize("foo")).setMessageId(7)))); assertThat(send.getFuture().isDone(), is(false)); cache.receive(Message.MSGACK(msg)); assertThat(send.getResult(), is(nullValue())); } /** * When CHNGD_OWNR is received, resend message to new owner */ @Test public void testSendToOwner4() throws Exception { setCommMsgCounter(); PUT(1234L, sh(10), 1L, "xxx"); INV(1234L, sh(10)); verify(comm).send(argThat(equalTo(Message.INVACK(Message.INV(sh(10), 1234L, sh(-1))).setMessageId(2)))); MSG msg = Message.MSG(sh(-1), 1234L, true, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(PENDING)); verify(comm).send(argThat(equalTo(Message.MSG(sh(10), 1234L, true, serialize("foo")).setMessageId(3)))); assertThat(send.getFuture().isDone(), is(false)); when(cluster.getMaster(sh(20))).thenReturn(makeNodeInfo(sh(20))); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(20), true)); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(20), true)); // twice, but only resend once verify(comm).send(argThat(equalTo(Message.MSG(sh(20), 1234L, true, serialize("foo")).setMessageId(4)))); assertThat(send.getFuture().isDone(), is(false)); when(cluster.getMaster(sh(30))).thenReturn(makeNodeInfo(sh(30))); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(30), true)); cache.receive(Message.CHNGD_OWNR(msg, 1234L, sh(30), true)); // twice, but only resend once verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, true, serialize("foo")).setMessageId(5)))); assertThat(send.getFuture().isDone(), is(false)); cache.receive(Message.MSGACK(msg)); assertThat(send.getResult(), is(nullValue())); } @Test public void testSend1() throws Exception { CacheListener listener = mock(CacheListener.class); doOp(LSTN, 1234L, listener); PUTX(1234L, sh(10), 1L, "xxx"); MSG msg = Message.MSG(sh(-1), 1234L, false, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(not(PENDING))); verify(listener).messageReceived(serialize("foo")); } @Test public void testSend2() throws Exception { CacheListener listener = mock(CacheListener.class); PUTX(1234L, sh(10), 1L, "xxx"); MSG msg = Message.MSG(sh(-1), 1234L, false, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(not(PENDING))); verify(listener, never()).messageReceived(any(byte[].class)); doOp(LSTN, 1234L, listener); verify(listener).messageReceived(serialize("foo")); } @Test public void testSend3() throws Exception { setCommMsgCounter(); CacheListener listener = mock(CacheListener.class); doOp(LSTN, 1234L, listener); PUT(1234L, sh(10), 1L, "xxx"); MSG msg = Message.MSG(sh(-1), 1234L, false, serialize("foo")); Op send = new Op(SEND, 1234, msg, null); Object res = cache.runOp(send); assertThat(res, is(PENDING)); verify(listener, never()).messageReceived(any(byte[].class)); verify(comm).send(argThat(equalTo(Message.MSG(sh(10), 1234L, false, serialize("foo")).setMessageId(2)))); } @Test public void whenPendingMSGsAndGETXThenSend() throws Exception { PUTX(1234L, sh(10), 1L, "xxx"); cache.receive(Message.MSG(sh(20), 1234L, false, serialize("foo"))); cache.receive(Message.MSG(sh(20), 1234L, false, serialize("bar"))); GETX(1234, sh(30)); InOrder inOrder = inOrder(comm); inOrder.verify(comm).send(argThat(equalTo(Message.PUTX(Message.GET(sh(30), 1234L), 1234L, new short[0], 2, 1, toBuffer("xxx"))))); inOrder.verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, false, true, serialize("foo")).setReplyRequired(false)))); inOrder.verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, false, true, serialize("bar")).setReplyRequired(false)))); } @Test public void whenPUTXandPendingAndGETXThenSend() throws Exception { PUTX(1234L, sh(10), 2, 1L, "xxx"); GETX(1234, sh(30)); verify(comm, never()).send(argThat(equalTo(Message.PUTX(Message.GET(sh(30), 1234L), 1234L, new short[0], 2, 1, toBuffer("xxx"))))); cache.receive(Message.MSG(sh(20), 1234L, false, true, serialize("foo"))); verify(comm, never()).send(argThat(equalTo(Message.PUTX(Message.GET(sh(30), 1234L), 1234L, new short[0], 2, 1, toBuffer("xxx"))))); cache.receive(Message.MSG(sh(20), 1234L, false, true, serialize("bar"))); InOrder inOrder = inOrder(comm); inOrder.verify(comm).send(argThat(equalTo(Message.PUTX(Message.GET(sh(30), 1234L), 1234L, new short[0], 2, 1, toBuffer("xxx"))))); inOrder.verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, false, true, serialize("foo")).setReplyRequired(false)))); inOrder.verify(comm).send(argThat(equalTo(Message.MSG(sh(30), 1234L, false, true, serialize("bar")).setReplyRequired(false)))); } /** * When NodeNotFoundException is thrown during send INV, short-circuit an INVACK */ @Test public void whenINVAndNodeNotFoundThenINVACKSelf() throws Exception { doThrow(new NodeNotFoundException(sh(20))).when(comm).send(argThat(equalTo(Message.INV(sh(20), 1234L, sh(10))))); doThrow(new NodeNotFoundException(sh(40))).when(comm).send(argThat(equalTo(Message.INV(sh(40), 1234L, sh(10))))); PUTX(1234L, sh(10), 1, "hello", 20, 30, 40); cache.runOp(new Op(GETX, 1234L, null)); assertState(1234L, O, E); cache.receive(Message.INVACK(sh(30), 1234L)); if (hasServer) cache.receive(Message.INVACK(sh(0), 1234L)); assertState(1234L, E, null); // 20 and 40 were self-ACKed when the exception was thrown } /** * When NodeNotFoundException is thrown during send GET/X, short-circuit a CHNGD_OWNR */ @Test public void whenGETAndNodeNotFoundThenCHNGD_OWNRSelf() throws Exception { for (Op.Type getType : new Op.Type[]{GET, GETX}) { PUT(1234L, sh(10), 1L, "hello"); INV(1234L, sh(10)); PUT(2222L, sh(10), 1L, "foo"); // prevent dirty read of 1234 doThrow(new NodeNotFoundException(sh(10))).when(comm).send(argThat(equalTo(matchingMessage(getType, sh(10), 1234L)))); cache.runOp(new Op(getType, 1234L, null)); verify(comm).send(argThat(equalTo(matchingMessage(getType, sh(-1), 1234L)))); // a CHNGD_OWNR to -1 is received during get from 10 reset(); } } /** * When TIMEOUT is received, all pending ops should be interrupted with a TimeoutException. */ @Test public void whenTimeoutThenInterruptPendingOps() throws Exception { setCommMsgCounter(); final Op op1 = new Op(GET, 1L, null); final Op op2 = new Op(GETX, 1L, null); final Op op3 = new Op(SET, 1L, serialize("xxx"), null); final Op op4 = new Op(SEND, 1L, Message.MSG(sh(-1), 1234L, true, serialize("foo")), null); Object res1 = cache.runOp(op1); Object res2 = cache.runOp(op2); Object res3 = cache.runOp(op3); Object res4 = cache.runOp(op4); assertThat(res1, is(PENDING)); assertThat(res2, is(PENDING)); assertThat(res3, is(PENDING)); assertThat(res4, is(PENDING)); cache.receive(Message.TIMEOUT(new LineMessage(sh(1), Type.GET, 1L))); try { op1.getResult(); fail("TimeoutException not thrown"); } catch (Exception e) { assertThat(e.getCause(), is(instanceOf(TimeoutException.class))); } try { op2.getResult(); fail("TimeoutException not thrown"); } catch (Exception e) { assertThat(e.getCause(), is(instanceOf(TimeoutException.class))); } try { op3.getResult(); fail("TimeoutException not thrown"); } catch (Exception e) { assertThat(e.getCause(), is(instanceOf(TimeoutException.class))); } try { op4.getResult(); fail("TimeoutException not thrown"); } catch (Exception e) { assertThat(e.getCause(), is(instanceOf(TimeoutException.class))); } } /** * Verify LRU eviction of shared/invalidated lines */ @Test public void testEviction1() throws Exception { cache = makeCache(120); CacheListener listener = mock(CacheListener.class); cache.addCacheListener(listener); PUTX(101L, sh(10), 1L, "0123456789"); PUT(201L, sh(10), 1L, "0123456789"); PUTX(102L, sh(10), 1L, "0123456789"); PUT(202L, sh(10), 1L, "0123456789"); PUTX(13L, sh(10), 1L, "0123456789"); PUT(203L, sh(10), 1L, "0123456789"); PUTX(104L, sh(10), 1L, "0123456789"); PUTX(105L, sh(10), 1L, "0123456789"); PUT(204L, sh(10), 1L, "0123456789"); PUTX(16L, sh(10), 1L, "0123456789"); GETX(13, sh(10)); // -> I get(202); get(204); get(203); get(201); GETX(16, sh(10)); for (long i = 107; i <= 200; i++) PUTX(i, sh(10), 1L, "0123456789"); for (long i = 300; i <= 400; i++) PUT(i, sh(10), 1L, "0123456789"); InOrder inOrder = inOrder(listener); inOrder.verify(listener).evicted(cache, 13); inOrder.verify(listener).evicted(cache, 202); inOrder.verify(listener).evicted(cache, 204); inOrder.verify(listener).evicted(cache, 203); inOrder.verify(listener).evicted(cache, 201); inOrder.verify(listener).evicted(cache, 16); for (long i = 101; i <= 200; i++) verify(listener, never()).evicted(cache, i); } @Test public void whenNodeRemovedThenCleanUp() throws Exception { PUT(1L, sh(10), 1L, "11"); PUT(2L, sh(10), 1L, "22"); PUTX(3L, sh(20), 1L, "33", 10, 20, 30); PUT(4L, sh(20), 1L, "44"); CacheListener listener = mock(CacheListener.class); cache.addCacheListener(listener); cache.nodeRemoved(sh(10)); assertState(1L, I, null); assertState(2L, I, null); assertState(3L, O, null); assertState(4L, S, null); // verify(listener).evicted(1L); // verify(listener).evicted(2L); verify(listener, never()).evicted(cache, 3L); verify(listener, never()).evicted(cache, 4L); // assertThat(cache.getLine(1), is(nullValue())); // assertThat(cache.getLine(2), is(nullValue())); // assertThat(cache.getLine(3), is(not(nullValue()))); // assertThat(cache.getLine(4), is(not(nullValue()))); // check line 3's sharers by getx and verifying INV's cache.runOp(new Op(GETX, 3L, null)); verify(comm).send(argThat(equalTo(Message.INV(sh(20), 3L, sh(20))))); verify(comm).send(argThat(equalTo(Message.INV(sh(30), 3L, sh(20))))); if (hasServer) verify(comm).send(argThat(equalTo(Message.INV(Comm.SERVER, 3L, sh(20))))); verify(comm, never()).send(argThat(equalTo(Message.INV(sh(10), 3L, sh(20))))); } /** * Make sure an exception is thrown when putting or setting data larger than the maximum data item size. */ @Test public void whenPutOrSetOverMaxItemSizeThenThrowException() throws Exception { cache.setMaxItemSize(7); long id = put("0123456"); try { put("01234567"); fail("Exception not thrown"); } catch (RuntimeException e) { } set(id, "012345"); try { set(id, "01234567"); fail("Exception not thrown"); } catch (RuntimeException e) { } } /** * It is possible to set data items to null. */ @Test public void testNullData() throws Exception { long ref1 = put(null); assertThat(get(ref1), is(nullValue())); set(ref1, "foo"); assertThat(get(ref1), is("foo")); long ref2 = put("foo"); assertThat(get(ref2), is("foo")); set(ref2, null); assertThat(get(ref2), is(nullValue())); } /** * */ @Test public void testAlloc() throws Exception { final RefAllocationsListener listener = hasServer ? null : getRefAllocationListener(cache.getRefAllocator()); Object res; Op alloc; Transaction txn = cache.beginTransaction(); alloc = new Op(ALLOC, -1L, 10, txn); res = cache.runOp(alloc); assertThat(res, is(PENDING)); if (hasServer) verify(comm).send(argThat(equalTo(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT)))); else verify(cluster).allocateRefs(anyInt()); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 100, 3)); else listener.refsAllocated(100, 3); assertThat(alloc.getFuture().isDone(), is(false)); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 200, 30)); else listener.refsAllocated(200, 30); res = alloc.getResult(); assertThat((Long) res, is(200L)); for (int i = 0; i < 10; i++) { res = get(200L + i); assertThat(res, is(nullValue())); assertModified(200L + i, true); assertThat(cache.getLine(200L + i).isLocked(), is(true)); } res = doOp(ALLOC, -1L, 7, txn); assertThat((Long) res, is(210L)); alloc = new Op(ALLOC, -1L, 20, txn); res = cache.runOp(alloc); assertThat(res, is(PENDING)); if (hasServer) verify(comm, atLeastOnce()).send(argThat(equalTo(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT)))); else verify(cluster, atLeastOnce()).allocateRefs(anyInt()); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 300, 10)); else listener.refsAllocated(300, 10); assertThat(alloc.getFuture().isDone(), is(false)); if (hasServer) cache.receive(Message.ALLOCED_REF(Message.ALLOC_REF(Comm.SERVER, DEFAULT_ALLOC_COUNT), 400, 50)); else listener.refsAllocated(400, 50); res = alloc.getResult(); assertThat((Long) res, is(400L)); } @Ignore @Test public void testTransactions() { pending(); } ///////////////////////////////////////////////////////////////////////////////// private boolean hasServer() { return hasServer; //cluster.hasServer(); } private LineMessage matchingMessage(Op.Type type, short node, long line) { switch (type) { case GET: case GETS: return Message.GET(node, line); case GETX: return Message.GETX(node, line); default: throw new IllegalArgumentException("Type must be GET/GETS/GETX but is " + type); } } void makeShared(long id) { cache.receive(Message.GET(sh(10), id)); } void makeInvalid(short owner, long id) { cache.receive(Message.INV(owner, id, owner).setMessageId(++messageId)); } void PUT(long id, short owner, long version, String obj) { cache.receive(Message.PUT(owner, id, version, toBuffer(obj)).setMessageId(++messageId)); } void PUTX(long id, short owner, long version, String obj, int... sharers) { short[] ssharers = new short[sharers.length]; for (int i = 0; i < sharers.length; i++) ssharers[i] = (short) sharers[i]; cache.receive(Message.PUTX(Message.GETX(owner, id), id, ssharers, 0, version, toBuffer(obj)).setMessageId(++messageId)); //cache.receive(Message.BACKUPACK(sh(-1), id, version)); } void INVOKE(long id, short owner, long version, LineFunction function) { cache.receive(Message.INVOKE(owner, id, function)); } void PUTX(long id, short owner, long version, String obj) { PUTX(id, owner, 0, version, obj); } void PUTX(long id, short owner, int parts, long version, String obj) { cache.receive(Message.PUTX(Message.GETX(owner, id), id, new short[0], parts, version, toBuffer(obj)).setMessageId(++messageId)); if (hasServer()) cache.receive(Message.INVACK(Message.INV(sh(0), id, owner))); //cache.receive(Message.BACKUPACK(sh(-1), id, version)); } void GET(long id, short node) { cache.receive(Message.GET(node, id)); } void GETX(long id, short node) { cache.receive(Message.GETX(node, id)); } void INV(long id, short owner) { cache.receive(Message.INV(owner, id, owner).setMessageId(++messageId)); } void INVACK(long id, short owner) { cache.receive(Message.INVACK(owner, id)); } long put(String obj) throws Exception { getRefAllocationListener(cache.getRefAllocator()).refsAllocated(100, 1000); return (Long) doOp(PUT, -1L, serialize(obj)); } String get(long id) throws Exception { return deserialize((byte[]) doOp(GET, id)); } void set(long id, String obj) throws Exception { doOp(SET, id, serialize(obj)); } void set(long id, String obj, Transaction txn) throws Exception { doOp(SET, id, serialize(obj), txn); } void del(long id) throws Exception { doOp(DEL, id); } private Message captureMessage() throws Exception { ArgumentCaptor<Message> captor = (ArgumentCaptor) ArgumentCaptor.forClass(Message.class); verify(comm).send(captor.capture()); return captor.getValue(); } private NodeInfo makeNodeInfo(final short node) { return new NodeInfo() { @Override public String getName() { return "NODE-" + node; } @Override public short getNodeId() { return node; } @Override public Object get(String property) { throw new UnsupportedOperationException(); } @Override public Collection<String> getProperties() { throw new UnsupportedOperationException(); } }; } public static RefAllocationsListener getRefAllocationListener(RefAllocator allocator) { if (new MockUtil().isMock(allocator)) { try { return (RefAllocationsListener) capture(allocator, "addRefAllocationsListener", arg(RefAllocationsListener.class)); } catch (Exception e) { return null; } } else return allocator.getRefAllocationsListeners().iterator().next(); } void assertState(long id, State state, State nextState) { CacheLine line = cache.getLine(id); assertThat(line.getState(), is(state)); assertThat(line.getNextState(), is(nextState)); } void assertOwner(long id, short node) { CacheLine line = cache.getLine(id); assertThat(line.getOwner(), is(node)); } void assertVersion(long id, long version) { CacheLine line = cache.getLine(id); assertThat(line.getVersion(), is(version)); } void assertLocked(long id, boolean value) { CacheLine line = cache.getLine(id); assertThat(line.isLocked(), is(value)); } void assertModified(long id, boolean value) { CacheLine line = cache.getLine(id); assertThat(line.is(CacheLine.MODIFIED), is(value)); } void evict(long id, boolean invack) { CacheLine line = cache.getLine(id); cache.evictLine(line, invack); } static long id(long id) { return Cache.MAX_RESERVED_REF_ID + id; } static short sh(int x) { return (short) x; } static short[] sh(int... args) { final short[] array = new short[args.length]; for (int i = 0; i < args.length; i++) array[i] = (short) args[i]; return array; } static ByteBuffer toBuffer(String object) { return ByteBuffer.wrap(serialize(object)); } static byte[] serialize(String object) { return object != null ? object.getBytes(Charsets.UTF_8) : null; } static String deserialize(Object obj) { return deserialize((byte[]) obj); } static String deserialize(byte[] array) { return array != null ? new String(array, Charsets.UTF_8) : null; } static void pending() { fail("Test pending"); } public Object doOp(Op.Type type, long line, byte[] data, Object extra) throws TimeoutException { return cache.doOp(type, line, (Object) (data != null ? Arrays.copyOf(data, data.length) : null), extra, null); } public Object doOp(Op.Type type, long line, ByteBuffer data, Object extra) throws TimeoutException { return cache.doOp(type, line, (Object) data, extra, null); } public Object doOp(Op.Type type, long line, Persistable data, Object extra) throws TimeoutException { return cache.doOp(type, line, (Object) data, extra, null); } public Object doOp(Op.Type type, long line, Object extra) throws TimeoutException { return cache.doOp(type, line, (Object) null, extra, null); } public Object doOp(Op.Type type, long line) throws TimeoutException { return cache.doOp(type, line, (Object) null, null, null); } public Object doOp(Op.Type type, long line, byte[] data) throws TimeoutException { return doOp(type, line, data, null); } public Object doOp(Op.Type type, long line, ByteBuffer data) throws TimeoutException { return doOp(type, line, data, null); } public Object doOp(Op.Type type, long line, Persistable data) throws TimeoutException { return doOp(type, line, data, null); } public Object doOp(Op.Type type, long line, byte[] data, Object extra, Transaction txn) throws TimeoutException { return cache.doOp(type, line, (Object) (data != null ? Arrays.copyOf(data, data.length) : null), extra, txn); } public Object doOp(Op.Type type, long line, ByteBuffer data, Object extra, Transaction txn) throws TimeoutException { return cache.doOp(type, line, (Object) data, extra, txn); } public Object doOp(Op.Type type, long line, Persistable data, Object extra, Transaction txn) throws TimeoutException { return cache.doOp(type, line, (Object) data, extra, txn); } public Object doOp(Op.Type type, long line, Object extra, Transaction txn) throws TimeoutException { return cache.doOp(type, line, (Object) null, extra, txn); } public Object doOp(Op.Type type, long line, Transaction txn) throws TimeoutException { return cache.doOp(type, line, (Object) null, null, txn); } public Object doOp(Op.Type type, long line, byte[] data, Transaction txn) throws TimeoutException { return doOp(type, line, data, null, txn); } public Object doOp(Op.Type type, long line, ByteBuffer data, Transaction txn) throws TimeoutException { return doOp(type, line, data, null, txn); } public Object doOp(Op.Type type, long line, Persistable data, Transaction txn) throws TimeoutException { return doOp(type, line, data, null, txn); } private void setCommMsgCounter() throws NodeNotFoundException { doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { Message msg = (Message) invocation.getArguments()[0]; System.out.println("mock send msg " + msg); if (msg.getMessageId() < 0) msg.setMessageId(++messageId); return null; } }).when(comm).send(any(Message.class)); } }