/*
* Copyright Terracotta, Inc.
*
* 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.ehcache.impl.internal.store.heap;
import org.ehcache.Cache.Entry;
import org.ehcache.ValueSupplier;
import org.ehcache.config.Eviction;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.core.events.StoreEventDispatcher;
import org.ehcache.core.events.StoreEventSink;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.ehcache.expiry.Expiry;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.spi.function.NullaryFunction;
import org.ehcache.impl.copy.IdentityCopier;
import org.ehcache.impl.internal.store.heap.holders.CopiedOnHeapValueHolder;
import org.ehcache.core.spi.time.SystemTimeSource;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.impl.internal.util.StatisticsTestUtils;
import org.ehcache.core.spi.store.Store.Iterator;
import org.ehcache.core.spi.store.Store.ValueHolder;
import org.ehcache.core.spi.store.Store.RemoveStatus;
import org.ehcache.core.spi.store.Store.ReplaceStatus;
import org.ehcache.core.spi.store.events.StoreEventListener;
import org.ehcache.core.spi.store.tiering.CachingTier;
import org.ehcache.core.statistics.CachingTierOperationOutcomes;
import org.ehcache.core.statistics.StoreOperationOutcomes;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.mockito.InOrder;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.ehcache.impl.internal.util.Matchers.holding;
import static org.ehcache.impl.internal.util.Matchers.valueHeld;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public abstract class BaseOnHeapStoreTest {
private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException();
protected StoreEventDispatcher eventDispatcher;
protected StoreEventSink eventSink;
@Rule
public TestRule watchman = new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
if (e.getMessage().startsWith("test timed out after")) {
System.err.println(buildThreadDump());
}
}
private String buildThreadDump() {
StringBuilder dump = new StringBuilder();
dump.append("***** Test timeout - printing thread dump ****");
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> e : stackTraces.entrySet()) {
Thread thread = e.getKey();
dump.append(String.format(
"\"%s\" %s prio=%d tid=%d %s\njava.lang.Thread.State: %s",
thread.getName(),
(thread.isDaemon() ? "daemon" : ""),
thread.getPriority(),
thread.getId(),
Thread.State.WAITING.equals(thread.getState()) ?
"in Object.wait()" : thread.getState().name().toLowerCase(),
Thread.State.WAITING.equals(thread.getState()) ?
"WAITING (on object monitor)" : thread.getState()));
for (StackTraceElement stackTraceElement : e.getValue()) {
dump.append("\n at ");
dump.append(stackTraceElement);
}
dump.append("\n");
}
return dump.toString();
}
};
@Before
@SuppressWarnings("unchecked")
public void setUp() {
eventDispatcher = mock(StoreEventDispatcher.class);
eventSink = mock(StoreEventSink.class);
when(eventDispatcher.eventSink()).thenReturn(eventSink);
}
@Test
public void testEvictEmptyStoreDoesNothing() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
assertThat(store.evict(eventSink), is(false));
verify(eventSink, never()).evicted(anyString(), anyValueSupplier());
}
@Test
public void testEvictWithNoEvictionAdvisorDoesEvict() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
for (int i = 0; i < 100; i++) {
store.put(Integer.toString(i), Integer.toString(i));
}
assertThat(store.evict(eventSink), is(true));
assertThat(storeSize(store), is(99));
verify(eventSink, times(1)).evicted(anyString(), anyValueSupplier());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.EvictionOutcome.SUCCESS));
}
@Test
public void testEvictWithFullyAdvisedAgainstEvictionDoesEvict() throws Exception {
OnHeapStore<String, String> store = newStore(new EvictionAdvisor<String, String>() {
@Override
public boolean adviseAgainstEviction(String key, String value) {
return true;
}
});
StoreEventSink<String, String> eventSink = getStoreEventSink();
for (int i = 0; i < 100; i++) {
store.put(Integer.toString(i), Integer.toString(i));
}
assertThat(store.evict(eventSink), is(true));
assertThat(storeSize(store), is(99));
verify(eventSink, times(1)).evicted(anyString(), anyValueSupplier());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.EvictionOutcome.SUCCESS));
}
@Test
public void testEvictWithBrokenEvictionAdvisorDoesEvict() throws Exception {
OnHeapStore<String, String> store = newStore(new EvictionAdvisor<String, String>() {
@Override
public boolean adviseAgainstEviction(String key, String value) {
throw new UnsupportedOperationException("Broken advisor!");
}
});
StoreEventSink<String, String> eventSink = getStoreEventSink();
for (int i = 0; i < 100; i++) {
store.put(Integer.toString(i), Integer.toString(i));
}
assertThat(store.evict(eventSink), is(true));
assertThat(storeSize(store), is(99));
verify(eventSink, times(1)).evicted(anyString(), anyValueSupplier());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.EvictionOutcome.SUCCESS));
}
@Test
public void testGet() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
assertThat(store.get("key").value(), equalTo("value"));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.HIT));
}
@Test
public void testGetNoPut() throws Exception {
OnHeapStore<String, String> store = newStore();
assertThat(store.get("key"), nullValue());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.MISS));
}
@Test
public void testGetExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
assertThat(store.get("key").value(), equalTo("value"));
timeSource.advanceTime(1);
assertThat(store.get("key"), nullValue());
checkExpiryEvent(eventSink, "key", "value");
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.HIT, StoreOperationOutcomes.GetOutcome.MISS));
}
@Test
public void testGetNoExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(2, TimeUnit.MILLISECONDS)));
StoreEventSink<String, String> eventSink = getStoreEventSink();
store.put("key", "value");
timeSource.advanceTime(1);
assertThat(store.get("key").value(), equalTo("value"));
verify(eventSink, never()).expired(anyString(), anyValueSupplier());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.GetOutcome.HIT));
}
@Test
public void testAccessTime() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
store.put("key", "value");
long first = store.get("key").lastAccessTime(TimeUnit.MILLISECONDS);
assertThat(first, equalTo(timeSource.getTimeMillis()));
final long advance = 5;
timeSource.advanceTime(advance);
long next = store.get("key").lastAccessTime(TimeUnit.MILLISECONDS);
assertThat(next, equalTo(first + advance));
}
@Test
public void testContainsKey() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
assertThat(store.containsKey("key"), is(true));
}
@Test
public void testNotContainsKey() throws Exception {
OnHeapStore<String, String> store = newStore();
assertThat(store.containsKey("key"), is(false));
}
@Test
public void testContainsKeyExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
assertThat(store.containsKey("key"), is(false));
checkExpiryEvent(eventSink, "key", "value");
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testPut() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
verify(eventSink).created(eq("key"), eq("value"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.PutOutcome.PUT));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testPutOverwrite() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
store.put("key", "value2");
verify(eventSink).updated(eq("key"), argThat(holding("value")), eq("value2"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
assertThat(store.get("key").value(), equalTo("value2"));
}
@Test
public void testCreateTime() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
assertThat(store.containsKey("key"), is(false));
store.put("key", "value");
ValueHolder<String> valueHolder = store.get("key");
assertThat(timeSource.getTimeMillis(), equalTo(valueHolder.creationTime(TimeUnit.MILLISECONDS)));
}
@Test
public void testInvalidate() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
store.invalidate("key");
assertThat(store.get("key"), nullValue());
StatisticsTestUtils.validateStats(store, EnumSet.of(CachingTierOperationOutcomes.InvalidateOutcome.REMOVED));
}
@Test
public void testPutIfAbsentNoValue() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
ValueHolder<String> prev = store.putIfAbsent("key", "value");
assertThat(prev, nullValue());
verify(eventSink).created(eq("key"), eq("value"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.PUT));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testPutIfAbsentValuePresent() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
ValueHolder<String> prev = store.putIfAbsent("key", "value2");
assertThat(prev.value(), equalTo("value"));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.HIT));
}
@Test
public void testPutIfAbsentUpdatesAccessTime() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
assertThat(store.get("key"), nullValue());
store.putIfAbsent("key", "value");
long first = store.get("key").lastAccessTime(TimeUnit.MILLISECONDS);
timeSource.advanceTime(1);
long next = store.putIfAbsent("key", "value2").lastAccessTime(TimeUnit.MILLISECONDS);
assertThat(next - first, equalTo(1L));
}
@Test
public void testPutIfAbsentExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
ValueHolder<String> prev = store.putIfAbsent("key", "value2");
assertThat(prev, nullValue());
assertThat(store.get("key").value(), equalTo("value2"));
checkExpiryEvent(getStoreEventSink(), "key", "value");
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testRemove() throws StoreAccessException {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
store.remove("key");
assertThat(store.get("key"), nullValue());
verify(eventSink).removed(eq("key"), argThat(holding("value")));
verifyListenerReleaseEventsInOrder(eventDispatcher);
}
@Test
public void testRemoveTwoArgMatch() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
RemoveStatus removed = store.remove("key", "value");
assertThat(removed, equalTo(RemoveStatus.REMOVED));
verify(eventSink).removed(eq("key"), argThat(holding("value")));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED));
assertThat(store.get("key"), nullValue());
}
@Test
public void testRemoveTwoArgNoMatch() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
RemoveStatus removed = store.remove("key", "not value");
assertThat(removed, equalTo(RemoveStatus.KEY_PRESENT));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testRemoveTwoArgExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
assertThat(store.get("key").value(), equalTo("value"));
timeSource.advanceTime(1);
RemoveStatus removed = store.remove("key", "value");
assertThat(removed, equalTo(RemoveStatus.KEY_MISSING));
checkExpiryEvent(eventSink, "key", "value");
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testReplaceTwoArgPresent() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
ValueHolder<String> existing = store.replace("key", "value2");
assertThat(existing.value(), equalTo("value"));
assertThat(store.get("key").value(), equalTo("value2"));
verify(eventSink).updated(eq("key"), argThat(holding("value")), eq("value2"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.REPLACED));
}
@Test
public void testReplaceTwoArgAbsent() throws Exception {
OnHeapStore<String, String> store = newStore();
ValueHolder<String> existing = store.replace("key", "value");
assertThat(existing, nullValue());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.MISS));
assertThat(store.get("key"), nullValue());
}
@Test
public void testReplaceTwoArgExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
ValueHolder<String> existing = store.replace("key", "value2");
assertThat(existing, nullValue());
assertThat(store.get("key"), nullValue());
checkExpiryEvent(eventSink, "key", "value");
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testReplaceThreeArgMatch() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
ReplaceStatus replaced = store.replace("key", "value", "value2");
assertThat(replaced, equalTo(ReplaceStatus.HIT));
assertThat(store.get("key").value(), equalTo("value2"));
verify(eventSink).updated(eq("key"), argThat(holding("value")), eq("value2"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED));
}
@Test
public void testReplaceThreeArgNoMatch() throws Exception {
OnHeapStore<String, String> store = newStore();
ReplaceStatus replaced = store.replace("key", "value", "value2");
assertThat(replaced, equalTo(ReplaceStatus.MISS_NOT_PRESENT));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.MISS));
store.put("key", "value");
replaced = store.replace("key", "not value", "value2");
assertThat(replaced, equalTo(ReplaceStatus.MISS_PRESENT));
StatisticsTestUtils.validateStat(store, StoreOperationOutcomes.ConditionalReplaceOutcome.MISS, 2L);
}
@Test
public void testReplaceThreeArgExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
ReplaceStatus replaced = store.replace("key", "value", "value2");
assertThat(replaced, equalTo(ReplaceStatus.MISS_NOT_PRESENT));
assertThat(store.get("key"), nullValue());
checkExpiryEvent(eventSink, "key", "value");
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testIterator() throws Exception {
OnHeapStore<String, String> store = newStore();
Iterator<Entry<String, ValueHolder<String>>> iter = store.iterator();
assertThat(iter.hasNext(), equalTo(false));
try {
iter.next();
fail("NoSuchElementException expected");
} catch (NoSuchElementException nse) {
// expected
}
store.put("key1", "value1");
iter = store.iterator();
assertThat(iter.hasNext(), equalTo(true));
assertEntry(iter.next(), "key1", "value1");
assertThat(iter.hasNext(), equalTo(false));
store.put("key2", "value2");
Map<String, String> observed = observe(store.iterator());
assertThat(2, equalTo(observed.size()));
assertThat(observed.get("key1"), equalTo("value1"));
assertThat(observed.get("key2"), equalTo("value2"));
}
@Test
public void testIteratorExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key1", "value1");
store.put("key2", "value2");
store.put("key3", "value3");
timeSource.advanceTime(1);
Map<String, String> observed = observe(store.iterator());
assertThat(3, equalTo(observed.size()));
assertThat(observed.get("key1"), equalTo("value1"));
assertThat(observed.get("key2"), equalTo("value2"));
assertThat(observed.get("key3"), equalTo("value3"));
StatisticsTestUtils.validateStat(store, StoreOperationOutcomes.ExpirationOutcome.SUCCESS, 0L);
}
@Test
public void testIteratorDoesNotUpdateAccessTime() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
store.put("key1", "value1");
store.put("key2", "value2");
timeSource.advanceTime(5);
Map<String, Long> times = observeAccessTimes(store.iterator());
assertThat(2, equalTo(times.size()));
assertThat(times.get("key1"), equalTo(0L));
assertThat(times.get("key2"), equalTo(0L));
}
@Test
public void testComputeReplaceTrue() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
ValueHolder<String> installedHolder = store.get("key");
long createTime = installedHolder.creationTime(TimeUnit.MILLISECONDS);
long accessTime = installedHolder.lastAccessTime(TimeUnit.MILLISECONDS);
timeSource.advanceTime(1);
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
return mappedValue;
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return true;
}
});
assertThat(newValue.value(), equalTo("value"));
assertThat(createTime + 1, equalTo(newValue.creationTime(TimeUnit.MILLISECONDS)));
assertThat(accessTime + 1, equalTo(newValue.lastAccessTime(TimeUnit.MILLISECONDS)));
verify(eventSink).updated(eq("key"), argThat(holding("value")), eq("value"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeOutcome.PUT));
}
@Test
public void testComputeReplaceFalse() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, Expirations.noExpiration());
store.put("key", "value");
ValueHolder<String> installedHolder = store.get("key");
long createTime = installedHolder.creationTime(TimeUnit.MILLISECONDS);
long accessTime = installedHolder.lastAccessTime(TimeUnit.MILLISECONDS);
timeSource.advanceTime(1);
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
return mappedValue;
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return false;
}
});
assertThat(newValue.value(), equalTo("value"));
assertThat(createTime, equalTo(newValue.creationTime(TimeUnit.MILLISECONDS)));
assertThat(accessTime + 1, equalTo(newValue.lastAccessTime(TimeUnit.MILLISECONDS)));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeOutcome.HIT));
}
@Test
public void testCompute() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
assertThat(mappedKey, equalTo("key"));
assertThat(mappedValue, nullValue());
return "value";
}
});
assertThat(newValue.value(), equalTo("value"));
verify(eventSink).created(eq("key"), eq("value"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeOutcome.PUT));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testComputeNull() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
return null;
}
});
assertThat(newValue, nullValue());
assertThat(store.get("key"), nullValue());
StatisticsTestUtils.validateStat(store, StoreOperationOutcomes.ComputeOutcome.MISS, 1L);
store.put("key", "value");
newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
return null;
}
});
assertThat(newValue, nullValue());
assertThat(store.get("key"), nullValue());
verify(eventSink).removed(eq("key"), argThat(holding("value")));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStat(store, StoreOperationOutcomes.ComputeOutcome.REMOVED, 1L);
}
@Test
public void testComputeException() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
try {
store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
throw RUNTIME_EXCEPTION;
}
});
fail("RuntimeException expected");
} catch (StoreAccessException cae) {
assertThat(cae.getCause(), is((Throwable)RUNTIME_EXCEPTION));
}
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testComputeExistingValue() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
store.put("key", "value");
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
assertThat(mappedKey, equalTo("key"));
assertThat(mappedValue, equalTo("value"));
return "value2";
}
});
assertThat(newValue.value(), equalTo("value2"));
verify(eventSink).updated(eq("key"), argThat(holding("value")), eq("value2"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeOutcome.PUT));
assertThat(store.get("key").value(), equalTo("value2"));
}
@Test
public void testComputeExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
StoreEventSink<String, String> eventSink = getStoreEventSink();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
ValueHolder<String> newValue = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String mappedKey, String mappedValue) {
assertThat(mappedKey, equalTo("key"));
assertThat(mappedValue, nullValue());
return "value2";
}
});
assertThat(newValue.value(), equalTo("value2"));
assertThat(store.get("key").value(), equalTo("value2"));
checkExpiryEvent(eventSink, "key", "value");
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testComputeWhenExpireOnCreate() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
timeSource.advanceTime(1000L);
OnHeapStore<String, String> store = newStore(timeSource, Expirations.builder().setCreate(Duration.ZERO).build());
ValueHolder<String> result = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String key, String value) {
return "value";
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return false;
}
});
assertThat(result, nullValue());
}
@Test
public void testComputeWhenExpireOnUpdate() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
timeSource.advanceTime(1000L);
OnHeapStore<String, String> store = newStore(timeSource, Expirations.builder().setUpdate(Duration.ZERO).build());
store.put("key", "value");
ValueHolder<String> result = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String key, String value) {
return "newValue";
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return false;
}
});
assertThat(result, valueHeld("newValue"));
}
@Test
public void testComputeWhenExpireOnAccess() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
timeSource.advanceTime(1000L);
OnHeapStore<String, String> store = newStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build());
store.put("key", "value");
ValueHolder<String> result = store.compute("key", new BiFunction<String, String, String>() {
@Override
public String apply(String key, String value) {
return value;
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return false;
}
});
assertThat(result, valueHeld("value"));
}
@Test
public void testComputeIfAbsent() throws Exception {
OnHeapStore<String, String> store = newStore();
StoreEventSink<String, String> eventSink = getStoreEventSink();
StoreEventDispatcher<String, String> eventDispatcher = getStoreEventDispatcher();
ValueHolder<String> newValue = store.computeIfAbsent("key", new Function<String, String>() {
@Override
public String apply(String mappedKey) {
assertThat(mappedKey, equalTo("key"));
return "value";
}
});
assertThat(newValue.value(), equalTo("value"));
verify(eventSink).created(eq("key"), eq("value"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.PUT));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testComputeIfAbsentExisting() throws Exception {
OnHeapStore<String, String> store = newStore();
store.put("key", "value");
ValueHolder<String> newValue = store.computeIfAbsent("key", new Function<String, String>() {
@Override
public String apply(String mappedKey) {
fail("Should not be called");
return null;
}
});
assertThat(newValue.value(), equalTo("value"));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.HIT));
assertThat(store.get("key").value(), equalTo("value"));
}
@Test
public void testComputeIfAbsentReturnNull() throws Exception {
OnHeapStore<String, String> store = newStore();
ValueHolder<String> newValue = store.computeIfAbsent("key", new Function<String, String>() {
@Override
public String apply(String mappedKey) {
return null;
}
});
assertThat(newValue, nullValue());
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.NOOP));
assertThat(store.get("key"), nullValue());
}
@Test
public void testComputeIfAbsentException() throws Exception {
OnHeapStore<String, String> store = newStore();
try {
store.computeIfAbsent("key", new Function<String, String>() {
@Override
public String apply(String mappedKey) {
throw RUNTIME_EXCEPTION;
}
});
fail("Expected exception");
} catch (StoreAccessException cae) {
assertThat(cae.getCause(), is((Throwable)RUNTIME_EXCEPTION));
}
assertThat(store.get("key"), nullValue());
}
@Test
@SuppressWarnings("unchecked")
public void testComputeIfAbsentExpired() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put("key", "value");
timeSource.advanceTime(1);
ValueHolder<String> newValue = store.computeIfAbsent("key", new Function<String, String>() {
@Override
public String apply(String mappedKey) {
assertThat(mappedKey, equalTo("key"));
return "value2";
}
});
assertThat(newValue.value(), equalTo("value2"));
assertThat(store.get("key").value(), equalTo("value2"));
final String value = "value";
verify(eventSink).expired(eq("key"), argThat(holding(value)));
verify(eventSink).created(eq("key"), eq("value2"));
verifyListenerReleaseEventsInOrder(eventDispatcher);
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testExpiryCreateException() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, new Expiry<String, String>() {
@Override
public Duration getExpiryForCreation(String key, String value) {
throw RUNTIME_EXCEPTION;
}
@Override
public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) {
throw new AssertionError();
}
@Override
public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) {
throw new AssertionError();
}
});
store.put("key", "value");
assertThat(store.containsKey("key"), equalTo(false));
}
@Test
public void testExpiryAccessExceptionReturnsValueAndExpiresIt() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
timeSource.advanceTime(5);
OnHeapStore<String, String> store = newStore(timeSource, new Expiry<String, String>() {
@Override
public Duration getExpiryForCreation(String key, String value) {
return Duration.INFINITE;
}
@Override
public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) {
throw RUNTIME_EXCEPTION;
}
@Override
public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) {
return null;
}
});
store.put("key", "value");
assertThat(store.get("key"), valueHeld("value"));
assertNull(store.get("key"));
}
@Test
public void testExpiryUpdateException() throws Exception{
final TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource, new Expiry<String, String>() {
@Override
public Duration getExpiryForCreation(String key, String value) {
return Duration.INFINITE;
}
@Override
public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) {
return Duration.INFINITE;
}
@Override
public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) {
if (timeSource.getTimeMillis() > 0) {
throw new RuntimeException();
}
return Duration.INFINITE;
}
});
store.put("key", "value");
store.get("key");
timeSource.advanceTime(1000);
store.put("key", "newValue");
assertNull(store.get("key"));
}
@Test
public void testGetOrComputeIfAbsentExpiresOnHit() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
@SuppressWarnings("unchecked")
CachingTier.InvalidationListener<String, String> invalidationListener = mock(CachingTier.InvalidationListener.class);
store.setInvalidationListener(invalidationListener);
store.put("key", "value");
assertThat(storeSize(store), is(1));
timeSource.advanceTime(1);
ValueHolder<String> newValue = store.getOrComputeIfAbsent("key", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
return null;
}
});
assertThat(newValue, nullValue());
assertThat(store.get("key"), nullValue());
verify(invalidationListener).onInvalidation(eq("key"), argThat(valueHeld("value")));
assertThat(storeSize(store), is(0));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testGetOfComputeIfAbsentExpiresWithLoaderWriter() throws Exception {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStore<String, String> store = newStore(timeSource,
Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
@SuppressWarnings("unchecked")
CachingTier.InvalidationListener<String, String> invalidationListener = mock(CachingTier.InvalidationListener.class);
store.setInvalidationListener(invalidationListener);
// Add an entry
store.put("key", "value");
assertThat(storeSize(store), is(1));
// Advance after expiration time
timeSource.advanceTime(1);
@SuppressWarnings("unchecked")
final ValueHolder<String> vh = mock(ValueHolder.class);
when(vh.value()).thenReturn("newvalue");
when(vh.expirationTime(TimeUnit.MILLISECONDS)).thenReturn(2L);
ValueHolder<String> newValue = store.getOrComputeIfAbsent("key", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
return vh;
}
});
assertThat(newValue, valueHeld("newvalue"));
assertThat(store.get("key"), valueHeld("newvalue"));
verify(invalidationListener).onInvalidation(eq("key"), argThat(valueHeld("value")));
assertThat(storeSize(store), is(1));
StatisticsTestUtils.validateStats(store, EnumSet.of(StoreOperationOutcomes.ExpirationOutcome.SUCCESS));
}
@Test
public void testGetOrComputeIfAbsentRemovesFault() throws StoreAccessException {
final OnHeapStore<String, String> store = newStore();
final CountDownLatch testCompletionLatch = new CountDownLatch(1);
final CountDownLatch threadFaultCompletionLatch = new CountDownLatch(1);
final CountDownLatch mainFaultCreationLatch = new CountDownLatch(1);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
try {
mainFaultCreationLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
store.invalidate("1");
store.getOrComputeIfAbsent("1", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
threadFaultCompletionLatch.countDown();
try {
testCompletionLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
});
} catch (StoreAccessException e) {
e.printStackTrace();
}
}
});
thread.start();
store.getOrComputeIfAbsent("1", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
try {
mainFaultCreationLatch.countDown();
threadFaultCompletionLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
});
testCompletionLatch.countDown();
}
@Test
public void testGetOrComputeIfAbsentInvalidatesFault() throws StoreAccessException, InterruptedException {
final OnHeapStore<String, String> store = newStore();
final CountDownLatch testCompletionLatch = new CountDownLatch(1);
final CountDownLatch threadFaultCompletionLatch = new CountDownLatch(1);
CachingTier.InvalidationListener<String, String> invalidationListener = new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(final String key, final ValueHolder<String> valueHolder) {
try {
valueHolder.getId();
} catch (Exception e) {
e.printStackTrace();
fail("Test tried to invalidate Fault");
}
}
};
store.setInvalidationListener(invalidationListener);
updateStoreCapacity(store, 1);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
store.getOrComputeIfAbsent("1", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
threadFaultCompletionLatch.countDown();
try {
testCompletionLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
});
} catch (StoreAccessException e) {
e.printStackTrace();
}
}
});
thread.start();
threadFaultCompletionLatch.await();
store.getOrComputeIfAbsent("10", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(final String s) {
return null;
}
});
testCompletionLatch.countDown();
}
@Test
public void testGetOrComputeIfAbsentContention() throws InterruptedException {
final OnHeapStore<String, String> store = newStore();
int threads = 10;
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(threads);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
startLatch.await();
} catch (InterruptedException e) {
fail("Got an exception waiting to start thread " + e);
}
try {
ValueHolder<String> result = store.getOrComputeIfAbsent("42", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(String key) {
return new CopiedOnHeapValueHolder<String>("theAnswer!", System.currentTimeMillis(), -1, false, new IdentityCopier<String>());
}
});
assertThat(result.value(), is("theAnswer!"));
endLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
fail("Got an exception " + e);
}
}
};
for (int i = 0; i < threads; i++) {
new Thread(runnable).start();
}
startLatch.countDown();
boolean result = endLatch.await(2, TimeUnit.SECONDS);
if (!result) {
fail("Wait expired before completion, logs should have exceptions");
}
}
@Test
@SuppressWarnings("unchecked")
public void testConcurrentFaultingAndInvalidate() throws Exception {
final OnHeapStore<String, String> store = newStore();
CachingTier.InvalidationListener<String, String> invalidationListener = mock(CachingTier.InvalidationListener.class);
store.setInvalidationListener(invalidationListener);
final AtomicReference<AssertionError> failedInThread = new AtomicReference<AssertionError>();
final CountDownLatch getLatch = new CountDownLatch(1);
final CountDownLatch invalidateLatch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
try {
store.getOrComputeIfAbsent("42", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(String aString) {
invalidateLatch.countDown();
try {
boolean await = getLatch.await(5, TimeUnit.SECONDS);
if (!await) {
failedInThread.set(new AssertionError("latch timed out"));
}
} catch (InterruptedException e) {
failedInThread.set(new AssertionError("Interrupted exception: " + e.getMessage()));
}
return new CopiedOnHeapValueHolder<String>("TheAnswer!", System.currentTimeMillis(), false, new IdentityCopier<String>());
}
});
} catch (StoreAccessException caex) {
failedInThread.set(new AssertionError("StoreAccessException: " + caex.getMessage()));
}
}
}).start();
boolean await = invalidateLatch.await(5, TimeUnit.SECONDS);
if (!await) {
fail("latch timed out");
}
store.invalidate("42");
getLatch.countDown();
verify(invalidationListener, never()).onInvalidation(any(String.class), any(ValueHolder.class));
if (failedInThread.get() != null) {
throw failedInThread.get();
}
}
@Test
public void testConcurrentSilentFaultingAndInvalidate() throws Exception {
final OnHeapStore<String, String> store = newStore();
final AtomicReference<AssertionError> failedInThread = new AtomicReference<AssertionError>();
final CountDownLatch getLatch = new CountDownLatch(1);
final CountDownLatch invalidateLatch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
try {
store.getOrComputeIfAbsent("42", new Function<String, ValueHolder<String>>() {
@Override
public ValueHolder<String> apply(String aString) {
invalidateLatch.countDown();
try {
boolean await = getLatch.await(5, TimeUnit.SECONDS);
if (!await) {
failedInThread.set(new AssertionError("latch timed out"));
}
} catch (InterruptedException e) {
failedInThread.set(new AssertionError("Interrupted exception: " + e.getMessage()));
}
return new CopiedOnHeapValueHolder<String>("TheAnswer!", System.currentTimeMillis(), false, new IdentityCopier<String>());
}
});
} catch (StoreAccessException caex) {
failedInThread.set(new AssertionError("StoreAccessException: " + caex.getMessage()));
}
}
}).start();
boolean await = invalidateLatch.await(5, TimeUnit.SECONDS);
if (!await) {
fail("latch timed out");
}
store.silentInvalidate("42", new Function<ValueHolder<String>, Void>() {
@Override
public Void apply(ValueHolder<String> stringValueHolder) {
if (stringValueHolder != null) {
assertThat("Expected a null parameter otherwise we leak a Fault", stringValueHolder, nullValue());
}
return null;
}
});
getLatch.countDown();
if (failedInThread.get() != null) {
throw failedInThread.get();
}
}
@Test(timeout = 2000L)
public void testEvictionDoneUnderEvictedKeyLockScope() throws Exception {
final OnHeapStore<String, String> store = newStore();
updateStoreCapacity(store, 2);
// Fill in store at capacity
store.put("keyA", "valueA");
store.put("keyB", "valueB");
final Exchanger<String> keyExchanger = new Exchanger<String>();
final AtomicReference<String> reference = new AtomicReference<String>();
final CountDownLatch faultingLatch = new CountDownLatch(1);
// prepare concurrent faulting
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String key;
try {
key = keyExchanger.exchange("ready to roll!");
store.put(key, "updateValue");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (StoreAccessException e) {
e.printStackTrace();
}
}
});
thread.start();
store.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, ValueHolder<String> valueHolder) {
// Only want to exchange on the first invalidation!
if (reference.compareAndSet(null, key)) {
try {
keyExchanger.exchange(key);
long now = System.nanoTime();
while (!thread.getState().equals(Thread.State.BLOCKED)) {
Thread.yield();
}
assertThat(thread.getState(), is(Thread.State.BLOCKED));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//trigger eviction
store.put("keyC", "valueC");
}
@Test(timeout = 2000L)
public void testIteratorExpiryHappensUnderExpiredKeyLockScope() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
final OnHeapStore<String, String> store = newStore(testTimeSource, Expirations.timeToLiveExpiration(new Duration(10, TimeUnit.MILLISECONDS)));
store.put("key", "value");
final CountDownLatch expiryLatch = new CountDownLatch(1);
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
expiryLatch.await();
store.put("key", "newValue");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (StoreAccessException e) {
e.printStackTrace();
}
}
});
thread.start();
store.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() {
@Override
public void onInvalidation(String key, ValueHolder<String> valueHolder) {
expiryLatch.countDown();
while (!thread.getState().equals(Thread.State.BLOCKED)) {
Thread.yield();
}
assertThat(thread.getState(), is(Thread.State.BLOCKED));
}
});
testTimeSource.advanceTime(20);
store.iterator();
}
@SuppressWarnings("unchecked")
private ValueSupplier<String> anyValueSupplier() {
return any(ValueSupplier.class);
}
private void verifyListenerReleaseEventsInOrder(StoreEventDispatcher<String, String> listener) {
StoreEventSink<String, String> eventSink = getStoreEventSink();
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).eventSink();
inOrder.verify(listener).releaseEventSink(eventSink);
}
private static int storeSize(OnHeapStore<?, ?> store) throws Exception {
int counter = 0;
Iterator<? extends Entry<?, ? extends ValueHolder<?>>> iterator = store.iterator();
while (iterator.hasNext()) {
iterator.next();
counter++;
}
return counter;
}
@SuppressWarnings("unchecked")
private <K, V> StoreEventListener<K, V> addListener(OnHeapStore<K, V> store) {
eventDispatcher = mock(StoreEventDispatcher.class);
eventSink = mock(StoreEventSink.class);
when(eventDispatcher.eventSink()).thenReturn(eventSink);
@SuppressWarnings("unchecked")
StoreEventListener<K, V> listener = mock(StoreEventListener.class);
return listener;
}
@SuppressWarnings("unchecked")
private static <K, V> void checkExpiryEvent(StoreEventSink<K, V> listener, final K key, final V value) {
verify(listener).expired(eq(key), argThat(holding(value)));
}
private static Map<String, Long> observeAccessTimes(Iterator<Entry<String, ValueHolder<String>>> iter)
throws StoreAccessException {
Map<String, Long> map = new HashMap<String, Long>();
while (iter.hasNext()) {
Entry<String, ValueHolder<String>> entry = iter.next();
map.put(entry.getKey(), entry.getValue().lastAccessTime(TimeUnit.MILLISECONDS));
}
return map;
}
private static Map<String, String> observe(Iterator<Entry<String, ValueHolder<String>>> iter)
throws StoreAccessException {
Map<String, String> map = new HashMap<String, String>();
while (iter.hasNext()) {
Entry<String, ValueHolder<String>> entry = iter.next();
map.put(entry.getKey(), entry.getValue().value());
}
return map;
}
private static void assertEntry(Entry<String, ValueHolder<String>> entry, String key, String value) {
assertThat(entry.getKey(), equalTo(key));
assertThat(entry.getValue().value(), equalTo(value));
}
private static class TestTimeSource implements TimeSource {
private long time = 0;
@Override
public long getTimeMillis() {
return time;
}
private void advanceTime(long delta) {
this.time += delta;
}
}
@SuppressWarnings("unchecked")
protected <K, V> StoreEventSink<K, V> getStoreEventSink() {
return eventSink;
}
@SuppressWarnings("unchecked")
protected <K, V> StoreEventDispatcher<K, V> getStoreEventDispatcher() {
return eventDispatcher;
}
protected <K, V> OnHeapStore<K, V> newStore() {
return newStore(SystemTimeSource.INSTANCE, Expirations.noExpiration(), Eviction.noAdvice());
}
protected <K, V> OnHeapStore<K, V> newStore(EvictionAdvisor<? super K, ? super V> evictionAdvisor) {
return newStore(SystemTimeSource.INSTANCE, Expirations.noExpiration(), evictionAdvisor);
}
protected <K, V> OnHeapStore<K, V> newStore(TimeSource timeSource, Expiry<? super K, ? super V> expiry) {
return newStore(timeSource, expiry, Eviction.noAdvice());
}
protected abstract void updateStoreCapacity(OnHeapStore<?, ?> store, int newCapacity);
protected abstract <K, V> OnHeapStore<K, V> newStore(final TimeSource timeSource,
final Expiry<? super K, ? super V> expiry, final EvictionAdvisor<? super K, ? super V> evictionAdvisor);
}