/* * Copyright 2015 Terracotta, Inc., a Software AG company. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terracotta.offheapstore.pinning; import org.terracotta.offheapstore.pinning.PinnableCache; import static org.terracotta.offheapstore.util.MemoryUnit.KILOBYTES; import static org.terracotta.offheapstore.util.MemoryUnit.MEGABYTES; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.junit.Assert; import org.junit.Test; import org.terracotta.offheapstore.buffersource.HeapBufferSource; import org.terracotta.offheapstore.exceptions.OversizeMappingException; import org.terracotta.offheapstore.paging.PageSource; import org.terracotta.offheapstore.paging.UpfrontAllocatingPageSource; import org.terracotta.offheapstore.util.PointerSizeParameterizedTest; public abstract class AbstractPinningIT extends PointerSizeParameterizedTest { protected abstract PinnableCache<Integer, Integer> createPinnedIntegerCache(PageSource source); protected abstract PinnableCache<Integer, byte[]> createPinnedByteArrayCache(PageSource source); protected abstract PinnableCache<Integer, byte[]> createSharingPinnedByteArrayCache(PageSource source); @Test public void testPinnedMappingStays() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), MEGABYTES.toBytes(2), MEGABYTES.toBytes(2)); PinnableCache<Integer, Integer> cache = createPinnedIntegerCache(source); cache.setPinning(-1, true); Assert.assertFalse(cache.isPinned(-1)); cache.putPinned(-1, 42); for (int i = 0; cache.size() == i + 1; i++) { cache.put(i, i); } Assert.assertTrue(cache.containsKey(-1)); int capacity = cache.size(); for (int i = 0; i < capacity * 10; i++) { cache.put(capacity + i, i); } Assert.assertEquals(Integer.valueOf(42), cache.get(-1)); Assert.assertTrue(cache.isPinned(-1)); } @Test public void testPinnedMappingCausesPutFailure() { final int cacheSize = KILOBYTES.toBytes(64); PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), cacheSize, cacheSize); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); int maximalSize = 0; for (int i = Integer.SIZE - (Integer.numberOfLeadingZeros(cacheSize) + 1); i >= 0; i--) { int bit = 1 << i; try { cache.put(0, new byte[maximalSize | bit]); maximalSize |= bit; } catch (OversizeMappingException e) { //ignore } cache.clear(); } Assert.assertNull(cache.putPinned(-1, new byte[maximalSize])); try { cache.put(0, new byte[1]); Assert.fail("Expected OversizeMappingException"); } catch (OversizeMappingException e) { Assert.assertEquals(1, cache.size()); } try { cache.setPinning(0, true); cache.put(0, new byte[1]); Assert.fail("Expected OversizeMappingException"); } catch (OversizeMappingException e) { Assert.assertEquals(1, cache.size()); } Assert.assertNotNull(cache.remove(-1)); Assert.assertNull(cache.get(-1)); Assert.assertTrue(cache.isEmpty()); Assert.assertNull(cache.put(0, new byte[1])); cache.setPinning(1, true); Assert.assertNull(cache.put(1, new byte[1])); Assert.assertNotNull(cache.get(0)); Assert.assertNotNull(cache.get(1)); Assert.assertEquals(2, cache.size()); } @Test public void testPinningWithSharedPageSource() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(256), KILOBYTES.toBytes(256)); PinnableCache<Integer, byte[]> cacheA = createSharingPinnedByteArrayCache(source); PinnableCache<Integer, byte[]> cacheB = createSharingPinnedByteArrayCache(source); int maxKey = 0; for (; cacheA.size() == maxKey; maxKey++) { cacheA.put(maxKey, new byte[128]); } Assert.assertNull(cacheA.putPinned(-1, new byte[128])); for (int i = 0; i < maxKey * 2; i++) { cacheB.put(i, new byte[128]); } Assert.assertTrue(cacheA.containsKey(-1)); Assert.assertNull(cacheB.putPinned(-1, new byte[128])); Assert.assertTrue(cacheA.containsKey(-1)); for (int i = maxKey + 1; i < maxKey * 3; i++) { cacheA.put(i, new byte[128]); } Assert.assertTrue(cacheB.containsKey(-1)); } @Test public void testPutIfAbsent() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, Integer> cache = createPinnedIntegerCache(source); Assert.assertNull(cache.putIfAbsent(-1, 42)); cache.setPinning(-1, true); for (int i = 0; cache.size() == i + 1; i++) { cache.put(i, i); } Assert.assertTrue(cache.containsKey(-1)); int capacity = cache.size(); for (int i = 0; i < capacity * 10; i++) { cache.put(capacity + i, i); } Assert.assertEquals(Integer.valueOf(42), cache.get(-1)); cache.setPinning(-1, false); Assert.assertEquals(Integer.valueOf(42), cache.put(-1, 43)); Assert.assertEquals(Integer.valueOf(43), cache.putIfAbsent(-1, 42)); Assert.assertEquals(Integer.valueOf(43), cache.get(-1)); for (int c = 0; c < 100 && cache.containsKey(-1); c++) { for (int i = 0; i < capacity; i++) { cache.put((c * capacity) + i, i); } } Assert.assertFalse(cache.containsKey(-1)); } @Test public void testReplaceThreeArg() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, Integer> cache = createPinnedIntegerCache(source); for (int i = 0; cache.size() == i; i++) { cache.put(i, i); } int capacity = cache.size(); cache.setPinning(-1, true); Assert.assertFalse(cache.replace(-1, 42, 43)); cache.setPinning(-1, false); Assert.assertNull(cache.get(-1)); Assert.assertNull(cache.put(-1, 42)); for (int c = 0; c < 100 && cache.containsKey(-1); c++) { for (int i = 0; i < capacity; i++) { cache.put((c * capacity) + i, i); } } Assert.assertFalse(cache.containsKey(-1)); Assert.assertNull(cache.put(-1, 42)); cache.setPinning(-1, true); Assert.assertFalse(cache.replace(-1, 41, 43)); cache.setPinning(-1, false); for (int c = 0; c < 100 && cache.containsKey(-1); c++) { for (int i = 0; i < capacity; i++) { cache.put((c * capacity) + i, i); } } Assert.assertFalse(cache.containsKey(-1)); Assert.assertNull(cache.put(-1, 42)); cache.setPinning(-1, true); Assert.assertTrue(cache.replace(-1, 42, 43)); Assert.assertFalse(cache.isPinned(-1)); } @Test public void testReplaceTwoArg() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, Integer> cache = createPinnedIntegerCache(source); for (int i = 0; cache.size() == i; i++) { cache.put(i, i); } int capacity = cache.size(); cache.setPinning(-1, true); Assert.assertNull(cache.replace(-1, 43)); cache.setPinning(-1, false); Assert.assertNull(cache.get(-1)); Assert.assertNull(cache.put(-1, 42)); for (int c = 0; c < 100 && cache.containsKey(-1); c++) { for (int i = 0; i < capacity; i++) { cache.put((c * capacity) + i, i); } } Assert.assertFalse(cache.containsKey(-1)); Assert.assertNull(cache.put(-1, 42)); cache.setPinning(-1, true); Assert.assertEquals(Integer.valueOf(42), cache.replace(-1, 43)); Assert.assertFalse(cache.isPinned(-1)); } @Test public void testPinUnpinWithPresentMapping() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); byte[] data = new byte[1024]; for (int i = 0 ; i < data.length; i++) { data[i] = (byte) i; } Assert.assertFalse(cache.isPinned(0)); cache.put(0, data); Assert.assertFalse(cache.isPinned(0)); cache.setPinning(0, true); Assert.assertTrue(cache.isPinned(0)); cache.setPinning(0, false); Assert.assertFalse(cache.isPinned(0)); byte[] value = cache.get(0); Assert.assertNotNull(value); Assert.assertArrayEquals(data, value); } @Test public void testRemoveWithPinnedMapping() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); Assert.assertFalse(cache.isPinned(0)); cache.put(0, new byte[1024]); Assert.assertFalse(cache.isPinned(0)); cache.setPinning(0, true); Assert.assertTrue(cache.isPinned(0)); cache.remove(0); Assert.assertFalse(cache.isPinned(0)); cache.setPinning(0, false); Assert.assertFalse(cache.isPinned(0)); byte[] value = cache.get(0); Assert.assertNull(value); } @Test public void testHighPinningFraction() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); int lastPut = 0; for (; cache.size() == lastPut; lastPut++) { if ((lastPut & 1) == 0) { cache.putPinned(lastPut, new byte[] {(byte) lastPut}); } else { cache.put(lastPut, new byte[] {(byte) lastPut}); } } for (int i = lastPut + 1; i < 10 * lastPut; i++) { cache.put(i, new byte[] {(byte) i}); } for (int i = 0; i < lastPut; i+= 2) { Assert.assertTrue(cache.containsKey(i)); } } @Test public void testAllMappingsPinned() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); for (int i = 0; true; i++) { try { cache.putPinned(i, new byte[] {(byte) i}); } catch (OversizeMappingException e) { break; } } int pinnedCount = cache.size(); for (int i = 0; i < pinnedCount; i++) { Assert.assertTrue(cache.containsKey(i)); } for (int i = -1; i >= -pinnedCount; i--) { try { cache.put(i, new byte[] {(byte) i}); } catch (OversizeMappingException e) { continue; } } for (int i = 0; i < pinnedCount; i++) { Assert.assertTrue(cache.containsKey(i)); } } @Test public void testMultiThreaded() throws ExecutionException, InterruptedException { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), KILOBYTES.toBytes(64), KILOBYTES.toBytes(64)); PinnableCache<Integer, byte[]> cache = createPinnedByteArrayCache(source); int lastPut = 0; for (; cache.size() == lastPut; lastPut++) { if ((lastPut & 1) == 0) { cache.putPinned(lastPut, new byte[] {(byte) lastPut}); } else { cache.put(lastPut, new byte[] {(byte) lastPut}); } } Collection<Callable<Void>> readers = new ArrayList<Callable<Void>>(); readers.add(new CacheAccessor(cache, lastPut + 1, lastPut * 10)); readers.add(new CacheAccessor(cache, (lastPut * 10) + 1, lastPut * 20)); ExecutorService executor = Executors.newFixedThreadPool(readers.size()); try { for (Future<Void> result : executor.invokeAll(readers)) { result.get(); } } finally { executor.shutdown(); } for (int i = 0; i < lastPut; i+= 2) { Assert.assertTrue(cache.containsKey(i)); } } static class CacheAccessor implements Callable<Void> { private final Map<Integer, byte[]> cache; private final int start; private final int end; CacheAccessor(Map<Integer, byte[]> cache, int start, int end) { this.cache = cache; this.start = start; this.end = end; } @Override public Void call() { for (int i = start; i < end; i++) { cache.put(i, new byte[] {(byte) i}); } return null; } } }