public ServerStoreImpl(ServerStoreConfiguration configuration, ResourcePageSource source, KeySegmentMapper mapper, List<OffHeapChainMap<Long>> recoveredMaps) { this.storeConfiguration = configuration; this.pageSource = source; this.store = new OffHeapServerStore(recoveredMaps, mapper); }
@Override public void append(long key, ByteBuffer payLoad) { checkPayLoadSize(payLoad); store.append(key, payLoad); }
@Override public long getAllocatedMemory() { return store.getAllocatedMemory(); }
@Override public void append(long key, ByteBuffer payLoad) { try { segmentFor(key).append(key, payLoad); } catch (OversizeMappingException e) { consumeOversizeMappingException(key, (long k) -> segmentFor(k).append(k, payLoad)); } }
@Override public Chain getAndAppend(long key, ByteBuffer payLoad) { try { return segmentFor(key).getAndAppend(key, payLoad); } catch (OversizeMappingException e) { return handleOversizeMappingException(key, (long k) -> segmentFor(k).getAndAppend(k, payLoad)); } }
@Test public void testServerSideUsageStats() { long maxBytes = MEGABYTES.toBytes(1); OffHeapServerStore store = new OffHeapServerStore(new UpfrontAllocatingPageSource(new OffHeapBufferSource(), maxBytes, MEGABYTES.toBytes(1)), new KeySegmentMapper(16), false); int oneKb = 1024; long smallLoopCount = 5; ByteBuffer smallValue = ByteBuffer.allocate(oneKb); for (long i = 0; i < smallLoopCount; i++) { store.getAndAppend(i, smallValue.duplicate()); } Assert.assertThat(store.getAllocatedMemory(),lessThanOrEqualTo(maxBytes)); Assert.assertThat(store.getAllocatedMemory(),greaterThanOrEqualTo(smallLoopCount * oneKb)); Assert.assertThat(store.getAllocatedMemory(),greaterThanOrEqualTo(store.getOccupiedMemory())); //asserts above already guarantee that occupiedMemory <= maxBytes and that occupiedMemory <= allocatedMemory Assert.assertThat(store.getOccupiedMemory(),greaterThanOrEqualTo(smallLoopCount * oneKb)); Assert.assertThat(store.getSize(), is(smallLoopCount)); int multiplier = 100; long largeLoopCount = 5 + smallLoopCount; ByteBuffer largeValue = ByteBuffer.allocate(multiplier * oneKb); for (long i = smallLoopCount; i < largeLoopCount; i++) { store.getAndAppend(i, largeValue.duplicate()); } Assert.assertThat(store.getAllocatedMemory(),lessThanOrEqualTo(maxBytes)); Assert.assertThat(store.getAllocatedMemory(),greaterThanOrEqualTo( (smallLoopCount * oneKb) + ( (largeLoopCount - smallLoopCount) * oneKb * multiplier) )); Assert.assertThat(store.getAllocatedMemory(),greaterThanOrEqualTo(store.getOccupiedMemory())); //asserts above already guarantee that occupiedMemory <= maxBytes and that occupiedMemory <= allocatedMemory Assert.assertThat(store.getOccupiedMemory(),greaterThanOrEqualTo(smallLoopCount * oneKb)); Assert.assertThat(store.getSize(), is(smallLoopCount + (largeLoopCount - smallLoopCount))); }
@Test public void test_getAndAppend_doesNotConsumeBuffer_evenWhenOversizeMappingException() throws Exception { OffHeapServerStore store = (OffHeapServerStore) spy(newStore()); final OffHeapChainMap<Object> offHeapChainMap = getOffHeapChainMapMock(); doThrow(OversizeMappingException.class).when(offHeapChainMap).getAndAppend(any(), any(ByteBuffer.class)); when(store.segmentFor(anyLong())).then(new Answer<Object>() { int invocations = 0; @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (invocations++ < 10) { return offHeapChainMap; } else { return invocation.callRealMethod(); } } }); when(store.tryShrinkOthers(anyLong())).thenReturn(true); ByteBuffer payload = createPayload(1L); store.getAndAppend(1L, payload); assertThat(payload.remaining(), is(8)); Chain expected = newChainBuilder().build(newElementBuilder().build(payload), newElementBuilder().build(payload)); Chain update = newChainBuilder().build(newElementBuilder().build(payload)); store.replaceAtHead(1L, expected, update); assertThat(payload.remaining(), is(8)); }
@Test public void put_worked_the_first_time_test() throws Exception { OffHeapChainMap<Long> offheapChainMap = getOffHeapChainMapLongMock(); ChainStorageEngine<Long> storageEngine = getChainStorageEngineLongMock(); when(offheapChainMap.getStorageEngine()).thenReturn(storageEngine); doNothing() .when(offheapChainMap).put(anyLong(), any(Chain.class)); OffHeapServerStore offHeapServerStore = new OffHeapServerStore(singletonList(offheapChainMap), mock(KeySegmentMapper.class)); offHeapServerStore.put(43L, mock(Chain.class)); }
@Test public void test_append_doesNotConsumeBuffer_evenWhenOversizeMappingException() throws Exception { OffHeapServerStore store = (OffHeapServerStore) spy(newStore()); final OffHeapChainMap<Object> offHeapChainMap = getOffHeapChainMapMock(); doThrow(OversizeMappingException.class).when(offHeapChainMap).append(any(Object.class), any(ByteBuffer.class)); when(store.segmentFor(anyLong())).then(new Answer<Object>() { int invocations = 0; @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (invocations++ < 10) { return offHeapChainMap; } else { return invocation.callRealMethod(); } } }); when(store.tryShrinkOthers(anyLong())).thenReturn(true); ByteBuffer payload = createPayload(1L); store.append(1L, payload); assertThat(payload.remaining(), is(8)); }
@Test public void test_replaceAtHead_doesNotConsumeBuffer_evenWhenOversizeMappingException() throws Exception { OffHeapServerStore store = (OffHeapServerStore) spy(newStore()); final OffHeapChainMap<Object> offHeapChainMap = getOffHeapChainMapMock(); doThrow(OversizeMappingException.class).when(offHeapChainMap).replaceAtHead(any(), any(Chain.class), any(Chain.class)); when(store.segmentFor(anyLong())).then(new Answer<Object>() { int invocations = 0; @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (invocations++ < 10) { return offHeapChainMap; } else { return invocation.callRealMethod(); } } }); when(store.tryShrinkOthers(anyLong())).thenReturn(true); ByteBuffer payload = createPayload(1L); Chain expected = newChainBuilder().build(newElementBuilder().build(payload), newElementBuilder().build(payload)); Chain update = newChainBuilder().build(newElementBuilder().build(payload)); store.replaceAtHead(1L, expected, update); assertThat(payload.remaining(), is(8)); }
@Test public void testCrossSegmentShrinking() { long seed = System.nanoTime(); Random random = new Random(seed); try { OffHeapServerStore store = new OffHeapServerStore(new UpfrontAllocatingPageSource(new OffHeapBufferSource(), MEGABYTES.toBytes(1L), MEGABYTES.toBytes(1)), DEFAULT_MAPPER, false); ByteBuffer smallValue = ByteBuffer.allocate(1024); for (int i = 0; i < 10000; i++) { try { store.getAndAppend(random.nextInt(500), smallValue.duplicate()); } catch (OversizeMappingException e) { //ignore } } ByteBuffer largeValue = ByteBuffer.allocate(100 * 1024); for (int i = 0; i < 10000; i++) { try { store.getAndAppend(random.nextInt(500), largeValue.duplicate()); } catch (OversizeMappingException e) { //ignore } } } catch (Throwable t) { throw (AssertionError) new AssertionError("Failed with seed " + seed).initCause(t); } }
boolean tryShrinkOthers(long key) { boolean evicted = false; OffHeapChainMap<Long> target = segmentFor(key); for (OffHeapChainMap<Long> s : segments) { if (s != target) { evicted |= s.shrink(); } } return evicted; }
@Test public void testGetMaxSize() { assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(2)), is(64L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(4)), is(128L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(16)), is(512L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(64)), is(2048L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(128)), is(4096L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(256)), is(8192L)); assertThat(OffHeapServerStore.getMaxSize(MEGABYTES.toBytes(512)), is(8192L)); assertThat(OffHeapServerStore.getMaxSize(GIGABYTES.toBytes(2)), is(8192L)); }
@Override public long getSize() { return store.getSize(); }
@Override public long getOccupiedMemory() { return store.getOccupiedMemory(); }
public void put(long key, Chain chain) { store.put(key, chain); }
@Override public void replaceAtHead(long key, Chain expect, Chain update) { store.replaceAtHead(key, expect, update); }
@Override public Chain getAndAppend(long key, ByteBuffer payLoad) { checkPayLoadSize(payLoad); return store.getAndAppend(key, payLoad); }
@Test(expected = OversizeMappingException.class) public void put_should_throw_when_underlying_put_always_throw_test() throws Exception { OffHeapChainMap<Long> offheapChainMap = getOffHeapChainMapLongMock(); ChainStorageEngine<Long> storageEngine = getChainStorageEngineLongMock(); when(offheapChainMap.getStorageEngine()).thenReturn(storageEngine); when(offheapChainMap.writeLock()).thenReturn(new ReentrantLock()); doThrow(new OversizeMappingException()) .when(offheapChainMap).put(anyLong(), any(Chain.class)); OffHeapServerStore offHeapServerStore = new OffHeapServerStore(singletonList(offheapChainMap), mock(KeySegmentMapper.class)); offHeapServerStore.put(43L, mock(Chain.class)); }
@Override public void replaceAtHead(long key, Chain expect, Chain update) { try { segmentFor(key).replaceAtHead(key, expect, update); } catch (OversizeMappingException e) { consumeOversizeMappingException(key, (long k) -> segmentFor(k).replaceAtHead(k, expect, update)); } }