blob: 4b965ce07d029fbeed33ceed0d6aa6fe90052eaf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jackrabbit.oak.segment;
import static org.apache.jackrabbit.oak.segment.SegmentCache.DEFAULT_SEGMENT_CACHE_MB;
import static org.apache.jackrabbit.oak.segment.SegmentCache.newSegmentCache;
import static org.apache.jackrabbit.oak.segment.SegmentStore.EMPTY_STORE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
import org.junit.Test;
public class SegmentCacheTest {
private final SegmentCache cache = newSegmentCache(DEFAULT_SEGMENT_CACHE_MB);
private final SegmentId id1 = new SegmentId(EMPTY_STORE, 0x0000000000000001L, 0xa000000000000001L, cache::recordHit);
private final Segment segment1 = mock(Segment.class);
private final SegmentId id2 = new SegmentId(EMPTY_STORE, 0x0000000000000002L, 0xa000000000000002L, cache::recordHit);
private final Segment segment2 = mock(Segment.class);
private final SegmentId id3 = new SegmentId(EMPTY_STORE, 0x0000000000000003L, 0xa000000000000003L, cache::recordHit);
private final Segment segment3 = mock(Segment.class);
{
when(segment1.getSegmentId()).thenReturn(id1);
when(segment1.estimateMemoryUsage()).thenReturn(1);
when(segment2.getSegmentId()).thenReturn(id2);
when(segment2.estimateMemoryUsage()).thenReturn(2);
when(segment3.getSegmentId()).thenReturn(id3);
when(segment3.estimateMemoryUsage()).thenReturn(DEFAULT_SEGMENT_CACHE_MB * 1024 * 1024);
}
@Test(expected = SegmentNotFoundException.class)
public void snfeFromUncachedSegment() {
id1.getSegment();
}
@Test
public void putTest() throws ExecutionException {
cache.putSegment(segment1);
// Segment should be memoised with its id
assertEquals(segment1, id1.getSegment());
// Segment should be cached with the segmentId and thus not trigger a call
// to the (empty) node store.
assertEquals(segment1, cache.getSegment(id1, () -> failToLoad(id1)));
}
@Test
public void invalidateTests() throws ExecutionException {
cache.putSegment(segment1);
assertEquals(segment1, id1.getSegment());
assertEquals(segment1, cache.getSegment(id1, () -> failToLoad(id1)));
// Clearing the cache should cause an eviction call back for id
cache.clear();
// Check eviction cleared memoisation
expect(SegmentNotFoundException.class, id1::getSegment);
// Check that segment1 was evicted and needs reloading through the node store
AtomicBoolean cached = new AtomicBoolean(true);
assertEquals(segment1, cache.getSegment(id1, () -> {
cached.set(false);
return segment1;
}));
assertFalse(cached.get());
// Assert that segment1 was loaded again
assertEquals(segment1, id1.getSegment());
assertEquals(segment1, cache.getSegment(id1, () -> failToLoad(id1)));
}
@Test
public void evictionDuringPut() throws ExecutionException {
cache.putSegment(segment3);
// Check eviction cleared memoisation
expect(SegmentNotFoundException.class, id3::getSegment);
// Check that segment3 was evicted inside put because of its size and needs
// reloading through the node store
AtomicBoolean cached = new AtomicBoolean(true);
assertEquals(segment3, cache.getSegment(id3, () -> {
cached.set(false);
return segment3;
}));
assertFalse(cached.get());
}
@Test
public void evictionDuringLoad() throws ExecutionException {
cache.getSegment(id3, () -> segment3);
// Check eviction cleared memoisation
expect(SegmentNotFoundException.class, id3::getSegment);
// Check that segment3 was evicted inside put because of its size and needs
// reloading through the node store
AtomicBoolean cached = new AtomicBoolean(true);
assertEquals(segment3, cache.getSegment(id3, () -> {
cached.set(false);
return segment3;
}));
assertFalse(cached.get());
}
@Test
public void nonEmptyCacheStatsTest() throws Exception {
AbstractCacheStats stats = cache.getCacheStats();
// empty cache
assertEquals(0, stats.getElementCount());
assertEquals(0, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
assertEquals(0, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
// load
cache.getSegment(id1, () -> segment1);
assertEquals(1, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(33, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(1, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
// cache hit
assertEquals(segment1, id1.getSegment());
assertEquals(1, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(33, stats.estimateCurrentWeight());
assertEquals(1, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(2, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
cache.clear();
assertEquals(0, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(1, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(2, stats.getRequestCount());
assertEquals(1, stats.getEvictionCount());
stats.resetStats();
assertEquals(0, stats.getElementCount());
assertEquals(0, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
assertEquals(0, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
// Eviction during put
cache.getSegment(id3, () -> segment3);
assertEquals(0, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(1, stats.getRequestCount());
assertEquals(1, stats.getEvictionCount());
}
@Test
public void emptyCacheStatsTest() throws Exception {
SegmentCache cache = newSegmentCache(0);
AbstractCacheStats stats = cache.getCacheStats();
// empty cache
assertEquals(0, stats.getElementCount());
assertEquals(0, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
assertEquals(0, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
// load
cache.getSegment(id1, () -> segment1);
assertEquals(0, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(1, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
// No cache hit
try {
id1.getSegment();
fail(id1 + " should not be in the cache");
} catch (SegmentNotFoundException expected) {}
cache.clear();
assertEquals(0, stats.getElementCount());
assertEquals(1, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(1, stats.getMissCount());
assertEquals(1, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
stats.resetStats();
assertEquals(0, stats.getElementCount());
assertEquals(0, stats.getLoadCount());
assertEquals(0, stats.estimateCurrentWeight());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
assertEquals(0, stats.getRequestCount());
assertEquals(0, stats.getEvictionCount());
}
private static void expect(Class<? extends Throwable> exceptionType, Callable<?> thunk) {
try {
thunk.call();
} catch (Throwable e) {
if (!exceptionType.isAssignableFrom(e.getClass())) {
throw new AssertionError(
"Unexpected exception: " + e.getClass().getSimpleName() + ". " +
"Expected: " + exceptionType.getSimpleName(), e);
} else {
return;
}
}
throw new AssertionError("Expected exception " +
exceptionType.getSimpleName() + " not thrown");
}
private static Segment failToLoad(SegmentId id) {
fail("Cache should not need to load " + id);
return null;
}
}