protected V silentPutIfAbsent(K key, V value) throws StoreAccessException { try { PutIfAbsentOperation<K, V> operation = new PutIfAbsentOperation<>(key, value, timeSource.getTimeMillis()); ByteBuffer payload = codec.encode(operation); long extractedKey = extractLongKey(key); Chain chain = storeProxy.getAndAppend(extractedKey, payload); ResolvedChain<K, V> resolvedChain = resolver.resolve(chain, key, timeSource.getTimeMillis()); if (resolvedChain.getCompactionCount() > chainCompactionLimit) { Chain compactedChain = resolvedChain.getCompactedChain(); storeProxy.replaceAtHead(extractedKey, chain, compactedChain); } Result<K, V> result = resolvedChain.getResolvedResult(key); return result == null ? null : result.getValue(); } catch (Exception re) { throw handleException(re); } }
protected ValueHolder<V> getInternal(K key) throws StoreAccessException, TimeoutException { ClusteredValueHolder<V> holder = null; try { Chain chain = storeProxy.get(extractLongKey(key)); if(!chain.isEmpty()) { ResolvedChain<K, V> resolvedChain = resolver.resolve(chain, key, timeSource.getTimeMillis()); if (resolvedChain.isCompacted()) { Chain compactedChain = resolvedChain.getCompactedChain(); storeProxy.replaceAtHead(extractLongKey(key), chain, compactedChain); } Result<K, V> resolvedResult = resolvedChain.getResolvedResult(key); if (resolvedResult != null) { V value = resolvedResult.getValue(); long expirationTime = resolvedChain.getExpirationTime(); if (expirationTime == Long.MAX_VALUE) { holder = new ClusteredValueHolder<>(value); } else { holder = new ClusteredValueHolder<>(value, expirationTime); } } } } catch (RuntimeException re) { throw handleException(re); } return holder; }
protected V silentRemove(K key, V value) throws StoreAccessException { try { ConditionalRemoveOperation<K, V> operation = new ConditionalRemoveOperation<>(key, value, timeSource.getTimeMillis()); ByteBuffer payload = codec.encode(operation); long extractedKey = extractLongKey(key); Chain chain = storeProxy.getAndAppend(extractedKey, payload); ResolvedChain<K, V> resolvedChain = resolver.resolve(chain, key, timeSource.getTimeMillis()); Result<K, V> result = resolvedChain.getResolvedResult(key); if (result != null && value.equals(result.getValue())) { storeProxy.replaceAtHead(extractedKey, chain, resolvedChain.getCompactedChain()); } return result == null ? null : result.getValue(); } catch (Exception re) { throw handleException(re); } }
@Test public void testResolveEmptyChain() throws Exception { Chain chain = getChainFromOperations(); EternalChainResolver<Long, String> resolver = new EternalChainResolver<>(codec); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); Result<Long, String> result = resolvedChain.getResolvedResult(1L); assertNull(result); assertThat(resolvedChain.isCompacted(), is(false)); }
@Test public void testResolveSingleRemove() throws Exception { Chain chain = getChainFromOperations(new RemoveOperation<>(1L, 0L)); EternalChainResolver<Long, String> resolver = new EternalChainResolver<>(codec); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); Result<Long, String> result = resolvedChain.getResolvedResult(1L); assertNull(result); assertThat(resolvedChain.isCompacted(), is(true)); assertThat(resolvedChain.getCompactionCount(), is(1)); }
@Test @SuppressWarnings("unchecked") public void testGetExpiryForUpdateUpdatesExpirationTimeStamp() { ExpiryPolicy<Long, String> expiry = mock(ExpiryPolicy.class); ExpiryChainResolver<Long, String> chainResolver = new ExpiryChainResolver<>(codec, expiry); when(expiry.getExpiryForUpdate(anyLong(), any(), anyString())).thenReturn(Duration.ofMillis(2L)); List<Operation<Long, String>> list = new ArrayList<>(); list.add(new PutOperation<>(1L, "Replaced", -10L)); list.add(new PutOperation<>(1L, "New", timeSource.getTimeMillis())); Chain chain = getChainFromOperations(list); ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis()); assertThat(resolvedChain.getResolvedResult(1L).getValue(), is("New")); assertTrue(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).isExpiryAvailable()); assertThat(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).expirationTime(), is(2L)); assertThat(resolvedChain.isCompacted(), is(true)); }
@Test public void testResolveForSingleOperationDoesNotCompact() { Chain chain = getChainFromOperations(new PutOperation<>(1L, "Albin", 0L)); EternalChainResolver<Long, String> resolver = new EternalChainResolver<>(codec); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); assertThat(resolvedChain.isCompacted(), is(false)); assertThat(resolvedChain.getCompactionCount(), is(0)); }
@Test @SuppressWarnings("unchecked") public void testRemoveDoesNotReplaceChainOnMisses() throws Exception { ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(null); //simulate a key miss on chain resolution EternalChainResolver<Long, String> resolver = mock(EternalChainResolver.class); when(resolver.resolve(any(Chain.class), anyLong(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); when(codec.encode(any())).thenReturn(ByteBuffer.allocate(0)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<>(config, codec, resolver, proxy, timeSource); store.remove(1L); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); }
@Test @SuppressWarnings("unchecked") public void testNullGetExpiryForCreation() { ExpiryPolicy<Long, String> expiry = mock(ExpiryPolicy.class); ExpiryChainResolver<Long, String> chainResolver = new ExpiryChainResolver<>(codec, expiry); when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(null); List<Operation<Long, String>> list = new ArrayList<>(); list.add(new PutOperation<>(1L, "Replaced", 10L)); Chain chain = getChainFromOperations(list); ResolvedChain<?, ?> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis()); assertTrue(resolvedChain.getCompactedChain().isEmpty()); assertThat(resolvedChain.isCompacted(), is(true)); }
@Test public void testNoExpireIsSentToHigherTiers() throws Exception { @SuppressWarnings("unchecked") Result<Long, String> result = mock(Result.class); when(result.getValue()).thenReturn("bar"); @SuppressWarnings("unchecked") ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); when(resolvedChain.getExpirationTime()).thenReturn(Long.MAX_VALUE); // no expire @SuppressWarnings("unchecked") EternalChainResolver<Long, String> resolver = mock(EternalChainResolver.class); when(resolver.resolve(any(Chain.class), anyLong(), anyLong())).thenReturn(resolvedChain); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.get(anyLong())).thenReturn(mock(Chain.class)); @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<>(config, codec, resolver, proxy, timeSource); Store.ValueHolder<?> vh = store.get(1L); long expirationTime = vh.expirationTime(); assertThat(expirationTime, is(NO_EXPIRE)); } }
@Test @SuppressWarnings("unchecked") public void testGetExpiryForAccessIsIgnored() { ExpiryPolicy<Long, String> expiry = mock(ExpiryPolicy.class); ExpiryChainResolver<Long, String> chainResolver = new ExpiryChainResolver<>(codec, expiry); when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(ExpiryPolicy.INFINITE); List<Operation<Long, String>> list = new ArrayList<>(); list.add(new PutOperation<>(1L, "One", timeSource.getTimeMillis())); list.add(new PutOperation<>(1L, "Second", timeSource.getTimeMillis())); Chain chain = getChainFromOperations(list); ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis()); verify(expiry, times(0)).getExpiryForAccess(anyLong(), any()); verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString()); verify(expiry, times(1)).getExpiryForUpdate(anyLong(), any(), anyString()); assertThat(resolvedChain.isCompacted(), is(true)); }
@Test public void testResolveSinglePut() throws Exception { Operation<Long, String> expected = new PutOperation<>(1L, "Albin", 0L); Chain chain = getChainFromOperations(expected); EternalChainResolver<Long, String> resolver = new EternalChainResolver<>(codec); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); Result<Long, String> result = resolvedChain.getResolvedResult(1L); assertEquals(expected, result); assertThat(resolvedChain.isCompacted(), is(false)); }
@Test public void testResolveRemovesOnly() throws Exception { Chain chain = getChainFromOperations( new RemoveOperation<>(1L, 0L), new RemoveOperation<>(1L, 0L)); EternalChainResolver<Long, String> resolver = new EternalChainResolver<>(codec); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); Result<Long, String> result = resolvedChain.getResolvedResult(1L); assertNull(result); assertThat(resolvedChain.isCompacted(), is(true)); assertThat(resolvedChain.getCompactionCount(), is(2)); }
@Test @SuppressWarnings("unchecked") public void testNullGetExpiryForUpdate() { ExpiryPolicy<Long, String> expiry = mock(ExpiryPolicy.class); ExpiryChainResolver<Long, String> chainResolver = new ExpiryChainResolver<>(codec, expiry); when(expiry.getExpiryForUpdate(anyLong(), any(), anyString())).thenReturn(null); List<Operation<Long, String>> list = new ArrayList<>(); list.add(new PutOperation<>(1L, "Replaced", -10L)); list.add(new PutOperation<>(1L, "New", timeSource.getTimeMillis())); Chain chain = getChainFromOperations(list); ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis()); assertThat(resolvedChain.getResolvedResult(1L).getValue(), is("New")); assertTrue(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).isExpiryAvailable()); assertThat(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).expirationTime(), is(10L)); assertThat(resolvedChain.isCompacted(), is(true)); }
@Test public void testResolveForSingleOperationDoesNotCompact() { Chain chain = getChainFromOperations(new PutOperation<>(1L, "Albin", 0L)); ExpiryChainResolver<Long, String> resolver = new ExpiryChainResolver<>(codec, ExpiryPolicyBuilder.noExpiration()); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 0L); assertThat(resolvedChain.isCompacted(), is(false)); assertThat(resolvedChain.getCompactionCount(), is(0)); }
protected boolean silentRemove(K key) throws StoreAccessException { try { RemoveOperation<K, V> operation = new RemoveOperation<>(key, timeSource.getTimeMillis()); ByteBuffer payload = codec.encode(operation); long extractedKey = extractLongKey(key); Chain chain = storeProxy.getAndAppend(extractedKey, payload); ResolvedChain<K, V> resolvedChain = resolver.resolve(chain, key, timeSource.getTimeMillis()); if(resolvedChain.getResolvedResult(key) != null) { storeProxy.replaceAtHead(extractedKey, chain, resolvedChain.getCompactedChain()); return true; } else { return false; } } catch (Exception re) { throw handleException(re); } }
@Test @SuppressWarnings("unchecked") public void testConditionalRemoveDoesNotReplaceChainOnKeyMiss() throws Exception { ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(null); //simulate a key miss on chain resolution EternalChainResolver<Long, String> resolver = mock(EternalChainResolver.class); when(resolver.resolve(any(Chain.class), anyLong(), anyLong())).thenReturn(resolvedChain); OperationsCodec<Long, String> codec = mock(OperationsCodec.class); when(codec.encode(any())).thenReturn(ByteBuffer.allocate(0)); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.getAndAppend(anyLong(), any(ByteBuffer.class))).thenReturn(mock(Chain.class)); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<>(config, codec, resolver, proxy, timeSource); store.remove(1L, "foo"); verify(proxy, never()).replaceAtHead(anyLong(), any(Chain.class), any(Chain.class)); }
@Test public void testResolveForMultipleOperationHasCorrectIsFirstAndTimeStampWithExpiry() { Chain chain = getChainFromOperations( new PutOperation<>(1L, "Albin1", 0L), new PutOperation<>(1L, "Albin2", 1L), new PutOperation<>(1L, "Albin3", 2L), new PutOperation<>(1L, "Albin4", 3L) ); ExpiryChainResolver<Long, String> resolver = new ExpiryChainResolver<>(codec, ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMillis(1L))); ResolvedChain<Long, String> resolvedChain = resolver.resolve(chain, 1L, 3L); Operation<Long, String> operation = codec.decode(resolvedChain.getCompactedChain().iterator().next().getPayload()); assertThat(operation.isExpiryAvailable(), is(true)); assertThat(operation.expirationTime(), is(4L)); try { operation.timeStamp(); fail(); } catch (Exception ex) { assertThat(ex.getMessage(), is("Timestamp not available")); } assertThat(resolvedChain.isCompacted(), is(true)); }
@Test public void testExpirationIsSentToHigherTiers() throws Exception { @SuppressWarnings("unchecked") Result<Long, String> result = mock(Result.class); when(result.getValue()).thenReturn("bar"); @SuppressWarnings("unchecked") ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.getResolvedResult(anyLong())).thenReturn(result); when(resolvedChain.getExpirationTime()).thenReturn(1000L); @SuppressWarnings("unchecked") EternalChainResolver<Long, String> resolver = mock(EternalChainResolver.class); when(resolver.resolve(any(Chain.class), anyLong(), anyLong())).thenReturn(resolvedChain); ServerStoreProxy proxy = mock(ServerStoreProxy.class); when(proxy.get(anyLong())).thenReturn(mock(Chain.class)); @SuppressWarnings("unchecked") OperationsCodec<Long, String> codec = mock(OperationsCodec.class); TimeSource timeSource = mock(TimeSource.class); ClusteredStore<Long, String> store = new ClusteredStore<>(config, codec, resolver, proxy, timeSource); Store.ValueHolder<?> vh = store.get(1L); long expirationTime = vh.expirationTime(); assertThat(expirationTime, is(1000L)); }
@Test public void testGetThatDoesNotCompactsInvokesReplace() throws Exception { TestTimeSource timeSource = new TestTimeSource(); timeSource.advanceTime(134556L); long now = timeSource.getTimeMillis(); OperationsCodec<Long, String> operationsCodec = new OperationsCodec<>(new LongSerializer(), new StringSerializer()); @SuppressWarnings("unchecked") EternalChainResolver<Long, String> chainResolver = mock(EternalChainResolver.class); @SuppressWarnings("unchecked") ResolvedChain<Long, String> resolvedChain = mock(ResolvedChain.class); when(resolvedChain.isCompacted()).thenReturn(false); when(chainResolver.resolve(any(Chain.class), eq(42L), eq(now))).thenReturn(resolvedChain); ServerStoreProxy serverStoreProxy = mock(ServerStoreProxy.class); Chain chain = mock(Chain.class); when(chain.isEmpty()).thenReturn(false); long longKey = HashUtils.intHashToLong(new Long(42L).hashCode()); when(serverStoreProxy.get(longKey)).thenReturn(chain); ClusteredStore<Long, String> clusteredStore = new ClusteredStore<>(config, operationsCodec, chainResolver, serverStoreProxy, timeSource); clusteredStore.get(42L); verify(serverStoreProxy, never()).replaceAtHead(eq(longKey), eq(chain), any(Chain.class)); }