@Test(timeout = 1000) public void readers_and_writers_must_block_on_evicting_page() throws Exception { // If we have a loaded page ... PageSwapper io = new BufferPageSwapper(ByteBuffer.allocate(TEST_PAGE_SIZE)); long pageId = 12; PinnablePage page = table.load(io, pageId, PageLock.EXCLUSIVE); monitor.observe(Fault.class); // ... a page that will take a long time to evict CountDownLatch latch = monitor.trap(is(new Evict(io, pageId))); // ... and a page that is soon up for eviction page.unpin(PageLock.EXCLUSIVE); // ... then when we observe the eviction taking place monitor.observe(Evict.class); // ... other threads should not be able to pin that page Thread pinForShared = fork($pinUnpin(page, io, pageId, PageLock.SHARED)); Thread pinForExclusive = fork($pinUnpin(page, io, pageId, PageLock.EXCLUSIVE)); awaitThreadState(pinForShared, Thread.State.WAITING); awaitThreadState(pinForExclusive, Thread.State.WAITING); // ... until the eviction finishes latch.countDown(); pinForShared.join(); pinForExclusive.join(); }
@Test public void evicting_must_flush_file() throws Exception { // Given a table with 1 entry, which I've modified ByteBuffer storageBuffer = ByteBuffer.allocate(TEST_PAGE_SIZE); BufferPageSwapper io = new BufferPageSwapper(storageBuffer); PinnablePage page = table.load(io, 12, PageLock.EXCLUSIVE); page.putBytes(bytesA, 0); page.unpin(PageLock.EXCLUSIVE); // When I perform an operation that will force eviction fork(new Runnable() { @Override public void run() { // This thread will cause the single page in the cache to be replaced BufferPageSwapper io = new BufferPageSwapper(ByteBuffer.allocate(1)); try { table.load(io, 3, PageLock.SHARED); } catch (IOException e) { e.printStackTrace(); } } }) .join(); // Then my changes should've been forced to disk byte[] actual = new byte[bytesA.length]; storageBuffer.position(0); storageBuffer.get(actual); assertThat(actual, equalTo(bytesA)); }
@Test public void must_notify_io_object_on_eviction() throws Exception { // Given BufferPageSwapper io = spy(new BufferPageSwapper(bytesA)); PinnablePage page = table.load(io, 12, PageLock.SHARED); page.unpin(PageLock.SHARED); // When Thread thread = fork( new Runnable() { @Override public void run() { // This thread will cause the single page in the cache to be replaced BufferPageSwapper io = new BufferPageSwapper(ByteBuffer.allocate(TEST_PAGE_SIZE)); try { table.load(io, 3, PageLock.SHARED).unpin(PageLock.SHARED); } catch (IOException e) { e.printStackTrace(); } } }); thread.join(); // Then verify(io).evicted(12); }
@Test(timeout = 1000) public void pinning_replaced_page_must_fail() throws Exception { // Given BufferPageSwapper io = new BufferPageSwapper(bytesA); PinnablePage page = table.load(io, 12, PageLock.SHARED); page.unpin(PageLock.SHARED); // When fork(new Runnable() { @Override public void run() { // This thread will cause the single page in the cache to be replaced BufferPageSwapper io = new BufferPageSwapper(ByteBuffer.wrap(bytesB)); try { table.load(io, 3, PageLock.SHARED).unpin(PageLock.SHARED); } catch (IOException e) { e.printStackTrace(); } } }) .join(); // Then assertFalse(page.pin(io, 12, PageLock.SHARED)); }
@Test(timeout = 1000) public void loading_with_exclusive_lock_stops_all_others() throws Exception { // Given ClockSweepPageTable table = new ClockSweepPageTable(1, TEST_PAGE_SIZE, PageCacheMonitor.NULL); final BufferPageSwapper io = new BufferPageSwapper(bytesA); // When final PinnablePage page = table.load(io, 12, PageLock.EXCLUSIVE); // Then we should have to wait for the page to be unpinned if we want an // exclusive lock on it. final AtomicBoolean acquiredPage = new AtomicBoolean(false); Thread otherThread = fork( new Runnable() { @Override public void run() { page.pin(io, 12, PageLock.SHARED); acquiredPage.set(true); } }); awaitThreadState(otherThread, Thread.State.WAITING); assertFalse(acquiredPage.get()); // And when I unpin mine, the other thread should get it page.unpin(PageLock.EXCLUSIVE); otherThread.join(); assertTrue(acquiredPage.get()); }
@Test public void loading_with_shared_lock_allows_other_shared() throws Exception { // Given BufferPageSwapper io = new BufferPageSwapper(bytesA); // When PinnablePage page = table.load(io, 12, PageLock.SHARED); // Then we should be able to grab another shared lock on it assertTrue(page.pin(io, 12, PageLock.SHARED)); }
@Test public void loading_must_read_file() throws Exception { // Given BufferPageSwapper io = new BufferPageSwapper(bytesA); // When PinnablePage page = table.load(io, 1, PageLock.EXCLUSIVE); // Then byte[] actual = new byte[bytesA.length]; page.getBytes(actual, 0); assertThat(actual, equalTo(bytesA)); }
@Test public void must_notify_monitor_of_evicted_pages() throws Exception { // If we load a page ... PageSwapper io = new BufferPageSwapper(ByteBuffer.allocate(TEST_PAGE_SIZE)); long pageId = 12; PinnablePage page = table.load(io, pageId, PageLock.EXCLUSIVE); page.unpin(PageLock.EXCLUSIVE); // ... then we should observe its page fault assertThat(monitor.observe(Fault.class), is(new Fault(io, pageId))); // ... and when it sits idle for long enough, we should observe its eviction assertThat(monitor.observe(Evict.class), is(new Evict(io, pageId))); }
@Test public void flushing_pages_with_specific_pageio_must_not_race_with_eviction() throws Exception { // The idea is that we repeatedly load a page with an EXCLUSIVE lock, and unpin it so it can // be evicted. As soon as we have unpinned, we repeatedly try to flush it with our given // PageIO. If this causes an exception to be thrown, then we've raced with the eviction // where we shouldn't. PageSwapper io = new BufferPageSwapper(ByteBuffer.allocate(TEST_PAGE_SIZE)); long pageId = 12; PinnablePage page = table.load(io, pageId, PageLock.EXCLUSIVE); monitor.observe(Fault.class); page.unpin(PageLock.EXCLUSIVE); // eviction is now possible LockSupport.unpark(sweeperThread); while (monitor.tryObserve(Evict.class) == null) { table.flush(io); } }