/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.geophile.erdo.map.diskmap.pagecache; import com.github.geophile.erdo.Configuration; import com.github.geophile.erdo.immutableitemcache.CacheEntryList; import com.github.geophile.erdo.immutableitemcache.ImmutableItemCache; import com.github.geophile.erdo.immutableitemcache.ImmutableItemCacheError; import com.github.geophile.erdo.immutableitemcache.ImmutableItemManager; import com.github.geophile.erdo.segmentfilemanager.pagememorymanager.PageMemoryManager; import com.github.geophile.erdo.segmentfilemanager.pagememorymanager.SubAllocatingPageMemoryManager; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import static java.lang.Math.abs; import static org.junit.Assert.*; public class PageCacheTest { @Before public void before() throws IOException, InterruptedException { Configuration configuration = Configuration.defaultConfiguration(); configuration.diskCacheSizeBytes(CACHE_SIZE); configuration.diskCacheSlabSizeBytes(SLAB_SIZE); configuration.diskPageSizeBytes(PAGE_SIZE); cacheObserver = new CacheObserver(); pageMemoryObserver = new PageMemoryObserver(); cache = new ImmutableItemCache<>(CACHE_CAPACITY, cacheObserver); pageReader = new PageManager(configuration); random = new Random(419); } @Test public void testOutOfPages() throws IOException, InterruptedException { // Use all available pages for (int i = 0; i < CACHE_CAPACITY; i++) { int r = abs(random.nextInt()); TestId id = new TestId(r, r); cache.find(id, pageReader); } // Page memory should be full assertEquals(pageMemoryObserver.pagesInUse, CACHE_CAPACITY); // Attempt to take next page should fail try { int r = abs(random.nextInt()); TestId id = new TestId(r, r); cache.find(id, pageReader); fail(); } catch (ImmutableItemCacheError e) { } } @Test public void testPageReplacement() throws IOException, InterruptedException { // Use all available pages for (int i = 0; i < CACHE_CAPACITY; i++) { int r = abs(random.nextInt()); TestId id = new TestId(r, r); TestPage page = cache.find(id, pageReader); } assertEquals(pageMemoryObserver.pagesInUse, CACHE_CAPACITY); // Keep loading pages, evicting pages and reusing memory final int N = 1000; for (int i = 0; i < N; i++) { TestPage evictable = randomPage(cacheObserver.pages); evictable.okToEvict(true); int r = abs(random.nextInt()); TestId id = new TestId(r, r); cache.find(id, pageReader); assertSame(evictable, cacheObserver.lastEvicted); } for (Map.Entry<TestId, TestPage> entry : cacheObserver.pages.entrySet()) { TestId id = entry.getKey(); TestPage page = entry.getValue(); byte signature = id.signature(); ByteBuffer buffer = page.buffer(); assertEquals(PAGE_SIZE, buffer.remaining()); for (int i = 0; i < PAGE_SIZE; i++) { assertEquals(signature, buffer.get(buffer.position() + i)); } } } private TestPage randomPage(Map<TestId, TestPage> pages) { Iterator<TestPage> iterator = pages.values().iterator(); int r = random.nextInt(pages.size()); for (int i = 0; i < r; i++) { iterator.next(); } return iterator.next(); } private static final int PAGE_SIZE = 5; private static final int CACHE_CAPACITY = 10; private static final int CACHE_SIZE = CACHE_CAPACITY * PAGE_SIZE; private static final int SLAB_SIZE = CACHE_SIZE / 2; private CacheObserver cacheObserver; private PageMemoryObserver pageMemoryObserver; private ImmutableItemCache<TestId, TestPage> cache; private PageManager pageReader; private Random random; private class PageManager implements ImmutableItemManager<TestId, TestPage> { @Override public TestPage getItemForCache(TestId id) throws IOException, InterruptedException { ByteBuffer buffer = pageMemoryManager.takePageBuffer(); assertEquals(PAGE_SIZE, buffer.remaining()); byte signature = id.signature(); for (int i = 0; i < PAGE_SIZE; i++) { buffer.put(signature); } buffer.position(0); return new TestPage(id, buffer); } @Override public void cleanupItemEvictedFromCache(TestPage testPage) throws IOException, InterruptedException { pageMemoryManager.returnPageBuffer(testPage.buffer()); } public PageManager(Configuration configuration) { pageMemoryManager = new SubAllocatingPageMemoryManager(configuration, pageMemoryObserver); } private PageMemoryManager pageMemoryManager; } private static class CacheObserver implements CacheEntryList.Observer<TestId, TestPage> { @Override public void adding(TestPage page) { pages.put(page.id(), page); } @Override public void evicting(TestPage victim) { pages.remove(victim.id()); lastEvicted = victim; } Map<TestId, TestPage> pages = new HashMap<TestId, TestPage>(); TestPage lastEvicted; } private static class PageMemoryObserver implements SubAllocatingPageMemoryManager.Observer { @Override public void takePageBuffer(int slabId, int offset) { pagesInUse++; lastTakenSlabId = slabId; lastTakenOffset = offset; } @Override public void returnPageBuffer(int slabId, int offset) { pagesInUse--; lastReturnedSlabId = slabId; lastReturnedOffset = offset; } int pagesInUse = 0; int lastTakenSlabId; int lastTakenOffset; int lastReturnedSlabId; int lastReturnedOffset; } }