/*
* 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;
import org.ehcache.config.SizedResourcePool;
import org.ehcache.core.CacheConfigurationChangeEvent;
import org.ehcache.core.CacheConfigurationChangeListener;
import org.ehcache.core.CacheConfigurationProperty;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.ResourceType;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.events.StoreEventDispatcher;
import org.ehcache.core.events.StoreEventSink;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.core.spi.store.heap.LimitExceededException;
import org.ehcache.expiry.Duration;
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.concurrent.ConcurrentHashMap;
import org.ehcache.impl.copy.SerializingCopier;
import org.ehcache.core.events.NullStoreEventDispatcher;
import org.ehcache.impl.internal.events.ScopedStoreEventDispatcher;
import org.ehcache.impl.internal.sizeof.NoopSizeOfEngine;
import org.ehcache.impl.internal.store.heap.holders.CopiedOnHeapValueHolder;
import org.ehcache.impl.internal.store.heap.holders.OnHeapValueHolder;
import org.ehcache.impl.internal.store.heap.holders.SerializedOnHeapValueHolder;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.core.spi.time.TimeSourceService;
import org.ehcache.impl.store.HashUtils;
import org.ehcache.impl.serialization.TransientStateRepository;
import org.ehcache.sizeof.annotations.IgnoreSizeOf;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.spi.serialization.StatefulSerializer;
import org.ehcache.spi.service.ServiceProvider;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.events.StoreEventSource;
import org.ehcache.core.spi.store.tiering.CachingTier;
import org.ehcache.core.spi.store.tiering.HigherCachingTier;
import org.ehcache.impl.internal.store.BinaryValueHolder;
import org.ehcache.spi.copy.Copier;
import org.ehcache.spi.copy.CopyProvider;
import org.ehcache.spi.service.Service;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceDependencies;
import org.ehcache.core.spi.store.heap.SizeOfEngine;
import org.ehcache.core.spi.store.heap.SizeOfEngineProvider;
import org.ehcache.core.statistics.CachingTierOperationOutcomes;
import org.ehcache.core.statistics.HigherCachingTierOperationOutcomes;
import org.ehcache.core.statistics.StoreOperationOutcomes;
import org.ehcache.core.collections.ConcurrentWeakIdentityHashMap;
import org.ehcache.core.statistics.TierOperationOutcomes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.offheapstore.util.FindbugsSuppressWarnings;
import org.terracotta.statistics.MappedOperationStatistic;
import org.terracotta.statistics.StatisticsManager;
import org.terracotta.statistics.observer.OperationObserver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.ehcache.config.Eviction.noAdvice;
import static org.ehcache.core.exceptions.StorePassThroughException.handleRuntimeException;
import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf;
import static org.terracotta.statistics.StatisticBuilder.operation;
/**
* {@link Store} and {@link HigherCachingTier} implementation for on heap.
*
* <p>
* It currently carries the following responsibilities:
* <ul>
* <li>Expiry</li>
* <li>Eviction</li>
* <li>Events</li>
* <li>Statistics</li>
* </ul>
*
* The storage of mappings is handled by a {@link ConcurrentHashMap} accessed through {@link Backend}.
*/
public class OnHeapStore<K, V> implements Store<K,V>, HigherCachingTier<K, V> {
private static final Logger LOG = LoggerFactory.getLogger(OnHeapStore.class);
private static final String STATISTICS_TAG = "OnHeap";
private static final int ATTEMPT_RATIO = 4;
private static final int EVICTION_RATIO = 2;
private static final EvictionAdvisor<Object, OnHeapValueHolder<?>> EVICTION_ADVISOR = new EvictionAdvisor<Object, OnHeapValueHolder<?>>() {
@Override
public boolean adviseAgainstEviction(Object key, OnHeapValueHolder<?> value) {
return value.evictionAdvice();
}
};
/**
* Comparator for eviction candidates:
* The highest priority is the ValueHolder having the smallest lastAccessTime.
*/
private static final Comparator<ValueHolder<?>> EVICTION_PRIORITIZER = new Comparator<ValueHolder<?>>() {
@Override
public int compare(ValueHolder<?> t, ValueHolder<?> u) {
if (t instanceof Fault) {
return -1;
} else if (u instanceof Fault) {
return 1;
} else {
return Long.signum(u.lastAccessTime(TimeUnit.NANOSECONDS) - t.lastAccessTime(TimeUnit.NANOSECONDS));
}
}
};
private static final InvalidationListener<?, ?> NULL_INVALIDATION_LISTENER = new InvalidationListener<Object, Object>() {
@Override
public void onInvalidation(Object key, ValueHolder<Object> valueHolder) {
// Do nothing
}
};
static final int SAMPLE_SIZE = 8;
private volatile Backend<K, V> map;
private final Class<K> keyType;
private final Class<V> valueType;
private final Copier<V> valueCopier;
private final SizeOfEngine sizeOfEngine;
private final boolean byteSized;
private volatile long capacity;
private final EvictionAdvisor<? super K, ? super V> evictionAdvisor;
private final Expiry<? super K, ? super V> expiry;
private final TimeSource timeSource;
private final StoreEventDispatcher<K, V> storeEventDispatcher;
@SuppressWarnings("unchecked")
private volatile InvalidationListener<K, V> invalidationListener = (InvalidationListener<K, V>) NULL_INVALIDATION_LISTENER;
private CacheConfigurationChangeListener cacheConfigurationChangeListener = new CacheConfigurationChangeListener() {
@Override
public void cacheConfigurationChange(CacheConfigurationChangeEvent event) {
if(event.getProperty().equals(CacheConfigurationProperty.UPDATE_SIZE)) {
ResourcePools updatedPools = (ResourcePools)event.getNewValue();
ResourcePools configuredPools = (ResourcePools)event.getOldValue();
if(updatedPools.getPoolForResource(ResourceType.Core.HEAP).getSize() !=
configuredPools.getPoolForResource(ResourceType.Core.HEAP).getSize()) {
LOG.info("Updating size to: {}", updatedPools.getPoolForResource(ResourceType.Core.HEAP).getSize());
SizedResourcePool pool = updatedPools.getPoolForResource(ResourceType.Core.HEAP);
if (pool.getUnit() instanceof MemoryUnit) {
capacity = ((MemoryUnit)pool.getUnit()).toBytes(pool.getSize());
} else {
capacity = pool.getSize();
}
}
}
}
};
private final OperationObserver<StoreOperationOutcomes.GetOutcome> getObserver;
private final OperationObserver<StoreOperationOutcomes.PutOutcome> putObserver;
private final OperationObserver<StoreOperationOutcomes.RemoveOutcome> removeObserver;
private final OperationObserver<StoreOperationOutcomes.PutIfAbsentOutcome> putIfAbsentObserver;
private final OperationObserver<StoreOperationOutcomes.ConditionalRemoveOutcome> conditionalRemoveObserver;
private final OperationObserver<StoreOperationOutcomes.ReplaceOutcome> replaceObserver;
private final OperationObserver<StoreOperationOutcomes.ConditionalReplaceOutcome> conditionalReplaceObserver;
private final OperationObserver<StoreOperationOutcomes.ComputeOutcome> computeObserver;
private final OperationObserver<StoreOperationOutcomes.ComputeIfAbsentOutcome> computeIfAbsentObserver;
private final OperationObserver<StoreOperationOutcomes.EvictionOutcome> evictionObserver;
private final OperationObserver<StoreOperationOutcomes.ExpirationOutcome> expirationObserver;
private final OperationObserver<CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome> getOrComputeIfAbsentObserver;
private final OperationObserver<CachingTierOperationOutcomes.InvalidateOutcome> invalidateObserver;
private final OperationObserver<CachingTierOperationOutcomes.InvalidateAllOutcome> invalidateAllObserver;
private final OperationObserver<CachingTierOperationOutcomes.InvalidateAllWithHashOutcome> invalidateAllWithHashObserver;
private final OperationObserver<HigherCachingTierOperationOutcomes.SilentInvalidateOutcome> silentInvalidateObserver;
private final OperationObserver<HigherCachingTierOperationOutcomes.SilentInvalidateAllOutcome> silentInvalidateAllObserver;
private final OperationObserver<HigherCachingTierOperationOutcomes.SilentInvalidateAllWithHashOutcome> silentInvalidateAllWithHashObserver;
private static final NullaryFunction<Boolean> REPLACE_EQUALS_TRUE = new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return Boolean.TRUE;
}
};
public OnHeapStore(final Configuration<K, V> config, final TimeSource timeSource, Copier<K> keyCopier, Copier<V> valueCopier, SizeOfEngine sizeOfEngine, StoreEventDispatcher<K, V> eventDispatcher) {
if (keyCopier == null) {
throw new NullPointerException("keyCopier must not be null");
}
if (valueCopier == null) {
throw new NullPointerException("valueCopier must not be null");
}
SizedResourcePool heapPool = config.getResourcePools().getPoolForResource(ResourceType.Core.HEAP);
if (heapPool == null) {
throw new IllegalArgumentException("OnHeap store must be configured with a resource of type 'heap'");
}
if (timeSource == null) {
throw new NullPointerException("timeSource must not be null");
}
if (sizeOfEngine == null) {
throw new NullPointerException("sizeOfEngine must not be null");
}
this.sizeOfEngine = sizeOfEngine;
this.byteSized = this.sizeOfEngine instanceof NoopSizeOfEngine ? false : true;
this.capacity = byteSized ? ((MemoryUnit) heapPool.getUnit()).toBytes(heapPool.getSize()) : heapPool.getSize();
this.timeSource = timeSource;
if (config.getEvictionAdvisor() == null) {
this.evictionAdvisor = noAdvice();
} else {
this.evictionAdvisor = config.getEvictionAdvisor();
}
this.keyType = config.getKeyType();
this.valueType = config.getValueType();
this.expiry = config.getExpiry();
this.valueCopier = valueCopier;
this.storeEventDispatcher = eventDispatcher;
if (keyCopier instanceof IdentityCopier) {
this.map = new SimpleBackend<K, V>(byteSized);
} else {
this.map = new KeyCopyBackend<K, V>(byteSized, keyCopier);
}
getObserver = operation(StoreOperationOutcomes.GetOutcome.class).named("get").of(this).tag(STATISTICS_TAG).build();
putObserver = operation(StoreOperationOutcomes.PutOutcome.class).named("put").of(this).tag(STATISTICS_TAG).build();
removeObserver = operation(StoreOperationOutcomes.RemoveOutcome.class).named("remove").of(this).tag(STATISTICS_TAG).build();
putIfAbsentObserver = operation(StoreOperationOutcomes.PutIfAbsentOutcome.class).named("putIfAbsent").of(this).tag(STATISTICS_TAG).build();
conditionalRemoveObserver = operation(StoreOperationOutcomes.ConditionalRemoveOutcome.class).named("conditionalRemove").of(this).tag(STATISTICS_TAG).build();
replaceObserver = operation(StoreOperationOutcomes.ReplaceOutcome.class).named("replace").of(this).tag(STATISTICS_TAG).build();
conditionalReplaceObserver = operation(StoreOperationOutcomes.ConditionalReplaceOutcome.class).named("conditionalReplace").of(this).tag(STATISTICS_TAG).build();
computeObserver = operation(StoreOperationOutcomes.ComputeOutcome.class).named("compute").of(this).tag(STATISTICS_TAG).build();
computeIfAbsentObserver = operation(StoreOperationOutcomes.ComputeIfAbsentOutcome.class).named("computeIfAbsent").of(this).tag(STATISTICS_TAG).build();
evictionObserver = operation(StoreOperationOutcomes.EvictionOutcome.class).named("eviction").of(this).tag(STATISTICS_TAG).build();
expirationObserver = operation(StoreOperationOutcomes.ExpirationOutcome.class).named("expiration").of(this).tag(STATISTICS_TAG).build();
getOrComputeIfAbsentObserver = operation(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.class).named("getOrComputeIfAbsent").of(this).tag(STATISTICS_TAG).build();
invalidateObserver = operation(CachingTierOperationOutcomes.InvalidateOutcome.class).named("invalidate").of(this).tag(STATISTICS_TAG).build();
invalidateAllObserver = operation(CachingTierOperationOutcomes.InvalidateAllOutcome.class).named("invalidateAll").of(this).tag(STATISTICS_TAG).build();
invalidateAllWithHashObserver = operation(CachingTierOperationOutcomes.InvalidateAllWithHashOutcome.class).named("invalidateAllWithHash").of(this).tag(STATISTICS_TAG).build();
silentInvalidateObserver = operation(HigherCachingTierOperationOutcomes.SilentInvalidateOutcome.class).named("silentInvalidate").of(this).tag(STATISTICS_TAG).build();
silentInvalidateAllObserver = operation(HigherCachingTierOperationOutcomes.SilentInvalidateAllOutcome.class).named("silentInvalidateAll").of(this).tag(STATISTICS_TAG).build();
silentInvalidateAllWithHashObserver = operation(HigherCachingTierOperationOutcomes.SilentInvalidateAllWithHashOutcome.class).named("silentInvalidateAllWithHash").of(this).tag(STATISTICS_TAG).build();
Set<String> tags = new HashSet<String>(Arrays.asList(STATISTICS_TAG, "tier"));
StatisticsManager.createPassThroughStatistic(this, "mappings", tags, new Callable<Number>() {
@Override
public Number call() throws Exception {
return map.mappingCount();
}
});
StatisticsManager.createPassThroughStatistic(this, "occupiedMemory", tags, new Callable<Number>() {
@Override
public Number call() throws Exception {
if (byteSized) {
return map.byteSize();
} else {
return -1L;
}
}
});
}
@Override
public ValueHolder<V> get(final K key) throws StoreAccessException {
checkKey(key);
return internalGet(key, true);
}
private OnHeapValueHolder<V> internalGet(final K key, final boolean updateAccess) throws StoreAccessException {
getObserver.begin();
try {
OnHeapValueHolder<V> mapping = getQuiet(key);
if (mapping == null) {
getObserver.end(StoreOperationOutcomes.GetOutcome.MISS);
return null;
}
if (updateAccess) {
setAccessTimeAndExpiryThenReturnMappingOutsideLock(key, mapping, timeSource.getTimeMillis());
}
getObserver.end(StoreOperationOutcomes.GetOutcome.HIT);
return mapping;
} catch (RuntimeException re) {
handleRuntimeException(re);
return null;
}
}
private OnHeapValueHolder<V> getQuiet(final K key) throws StoreAccessException {
try {
OnHeapValueHolder<V> mapping = map.get(key);
if (mapping == null) {
return null;
}
if (mapping.isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS)) {
expireMappingUnderLock(key, mapping);
return null;
}
return mapping;
} catch (RuntimeException re) {
handleRuntimeException(re);
return null;
}
}
@Override
public boolean containsKey(final K key) throws StoreAccessException {
checkKey(key);
return getQuiet(key) != null;
}
@Override
public PutStatus put(final K key, final V value) throws StoreAccessException {
putObserver.begin();
checkKey(key);
checkValue(value);
final long now = timeSource.getTimeMillis();
final AtomicReference<StoreOperationOutcomes.PutOutcome> statOutcome = new AtomicReference<StoreOperationOutcomes.PutOutcome>(StoreOperationOutcomes.PutOutcome.NOOP);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
updateUsageInBytesIfRequired(- mappedValue.size());
mappedValue = null;
}
if (mappedValue == null) {
OnHeapValueHolder<V> newValue = newCreateValueHolder(key, value, now, eventSink);
if (newValue != null) {
updateUsageInBytesIfRequired(newValue.size());
statOutcome.set(StoreOperationOutcomes.PutOutcome.PUT);
}
return newValue;
} else {
OnHeapValueHolder<V> newValue = newUpdateValueHolder(key, mappedValue, value, now, eventSink);
if (newValue != null) {
updateUsageInBytesIfRequired(newValue.size() - mappedValue.size());
} else {
updateUsageInBytesIfRequired(- mappedValue.size());
}
statOutcome.set(StoreOperationOutcomes.PutOutcome.REPLACED);
return newValue;
}
}
});
storeEventDispatcher.releaseEventSink(eventSink);
enforceCapacity();
StoreOperationOutcomes.PutOutcome outcome = statOutcome.get();
putObserver.end(outcome);
switch (outcome) {
case REPLACED:
return PutStatus.UPDATE;
case PUT:
return PutStatus.PUT;
case NOOP:
return PutStatus.NOOP;
default:
throw new AssertionError("Unknown enum value " + outcome);
}
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return PutStatus.NOOP;
}
}
@Override
public boolean remove(final K key) throws StoreAccessException {
removeObserver.begin();
checkKey(key);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
final long now = timeSource.getTimeMillis();
try {
final AtomicReference<StoreOperationOutcomes.RemoveOutcome> statisticOutcome = new AtomicReference<StoreOperationOutcomes.RemoveOutcome>(StoreOperationOutcomes.RemoveOutcome.MISS);
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
updateUsageInBytesIfRequired(- mappedValue.size());
if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
return null;
}
statisticOutcome.set(StoreOperationOutcomes.RemoveOutcome.REMOVED);
eventSink.removed(mappedKey, mappedValue);
return null;
}
});
storeEventDispatcher.releaseEventSink(eventSink);
StoreOperationOutcomes.RemoveOutcome outcome = statisticOutcome.get();
removeObserver.end(outcome);
switch (outcome) {
case REMOVED:
return true;
case MISS:
return false;
default:
throw new AssertionError("Unknown enum value " + outcome);
}
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return false;
}
}
@Override
public ValueHolder<V> putIfAbsent(final K key, final V value) throws StoreAccessException {
return putIfAbsent(key, value, false);
}
private OnHeapValueHolder<V> putIfAbsent(final K key, final V value, boolean returnCurrentMapping) throws StoreAccessException {
putIfAbsentObserver.begin();
checkKey(key);
checkValue(value);
final AtomicReference<OnHeapValueHolder<V>> returnValue = new AtomicReference<OnHeapValueHolder<V>>(null);
final AtomicBoolean entryActuallyAdded = new AtomicBoolean();
final long now = timeSource.getTimeMillis();
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
OnHeapValueHolder<V> inCache = map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
if (mappedValue != null) {
updateUsageInBytesIfRequired(- mappedValue.size());
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
}
OnHeapValueHolder<V> holder = newCreateValueHolder(key, value, now, eventSink);
if (holder != null) {
updateUsageInBytesIfRequired(holder.size());
}
entryActuallyAdded.set(holder != null);
return holder;
}
returnValue.set(mappedValue);
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
if (holder == null) {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
});
storeEventDispatcher.releaseEventSink(eventSink);
if (entryActuallyAdded.get()) {
enforceCapacity();
putIfAbsentObserver.end(StoreOperationOutcomes.PutIfAbsentOutcome.PUT);
} else {
putIfAbsentObserver.end(StoreOperationOutcomes.PutIfAbsentOutcome.HIT);
}
if (returnCurrentMapping) {
return inCache;
}
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
}
return returnValue.get();
}
@Override
public RemoveStatus remove(final K key, final V value) throws StoreAccessException {
conditionalRemoveObserver.begin();
checkKey(key);
checkValue(value);
final AtomicReference<RemoveStatus> outcome = new AtomicReference<RemoveStatus>(RemoveStatus.KEY_MISSING);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
final long now = timeSource.getTimeMillis();
if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
updateUsageInBytesIfRequired(- mappedValue.size());
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
return null;
} else if (value.equals(mappedValue.value())) {
updateUsageInBytesIfRequired(- mappedValue.size());
eventSink.removed(mappedKey, mappedValue);
outcome.set(RemoveStatus.REMOVED);
return null;
} else {
outcome.set(RemoveStatus.KEY_PRESENT);
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
if (holder == null) {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
});
storeEventDispatcher.releaseEventSink(eventSink);
RemoveStatus removeStatus = outcome.get();
switch (removeStatus) {
case REMOVED:
conditionalRemoveObserver.end(StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED);
break;
case KEY_MISSING:
case KEY_PRESENT:
conditionalRemoveObserver.end(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS);
break;
default:
}
return removeStatus;
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return RemoveStatus.KEY_MISSING;
}
}
@Override
public ValueHolder<V> replace(final K key, final V value) throws StoreAccessException {
replaceObserver.begin();
checkKey(key);
checkValue(value);
final AtomicReference<OnHeapValueHolder<V>> returnValue = new AtomicReference<OnHeapValueHolder<V>>(null);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
final long now = timeSource.getTimeMillis();
if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
updateUsageInBytesIfRequired(- mappedValue.size());
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
return null;
} else {
returnValue.set(mappedValue);
OnHeapValueHolder<V> holder = newUpdateValueHolder(key, mappedValue, value, now, eventSink);
if (holder != null) {
updateUsageInBytesIfRequired(holder.size() - mappedValue.size());
} else {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
});
OnHeapValueHolder<V> valueHolder = returnValue.get();
storeEventDispatcher.releaseEventSink(eventSink);
enforceCapacity();
if (valueHolder != null) {
replaceObserver.end(StoreOperationOutcomes.ReplaceOutcome.REPLACED);
} else {
replaceObserver.end(StoreOperationOutcomes.ReplaceOutcome.MISS);
}
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
}
return returnValue.get();
}
@Override
public ReplaceStatus replace(final K key, final V oldValue, final V newValue) throws StoreAccessException {
conditionalReplaceObserver.begin();
checkKey(key);
checkValue(oldValue);
checkValue(newValue);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
final AtomicReference<ReplaceStatus> outcome = new AtomicReference<ReplaceStatus>(ReplaceStatus.MISS_NOT_PRESENT);
try {
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
final long now = timeSource.getTimeMillis();
V existingValue = mappedValue.value();
if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
updateUsageInBytesIfRequired(- mappedValue.size());
return null;
} else if (oldValue.equals(existingValue)) {
outcome.set(ReplaceStatus.HIT);
OnHeapValueHolder<V> holder = newUpdateValueHolder(key, mappedValue, newValue, now, eventSink);
if (holder != null) {
updateUsageInBytesIfRequired(holder.size() - mappedValue.size());
} else {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
} else {
outcome.set(ReplaceStatus.MISS_PRESENT);
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
if (holder == null) {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
});
storeEventDispatcher.releaseEventSink(eventSink);
enforceCapacity();
ReplaceStatus replaceStatus = outcome.get();
switch (replaceStatus) {
case HIT:
conditionalReplaceObserver.end(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED);
break;
case MISS_PRESENT:
case MISS_NOT_PRESENT:
conditionalReplaceObserver.end(StoreOperationOutcomes.ConditionalReplaceOutcome.MISS);
break;
default:
throw new AssertionError("Unknown enum value " + replaceStatus);
}
return replaceStatus;
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return ReplaceStatus.MISS_NOT_PRESENT; // Not reached - above throws always
}
}
@Override
public void clear() {
this.map = map.clear();
}
@Override
public Iterator<Cache.Entry<K, ValueHolder<V>>> iterator() {
return new Iterator<Cache.Entry<K, ValueHolder<V>>>() {
private final java.util.Iterator<Map.Entry<K, OnHeapValueHolder<V>>> it = map.entrySetIterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Cache.Entry<K, ValueHolder<V>> next() throws StoreAccessException {
Entry<K, OnHeapValueHolder<V>> next = it.next();
final K key = next.getKey();
final OnHeapValueHolder<V> value = next.getValue();
return new Cache.Entry<K, ValueHolder<V>>() {
@Override
public K getKey() {
return key;
}
@Override
public ValueHolder<V> getValue() {
return value;
}
};
}
};
}
@Override
public ValueHolder<V> getOrComputeIfAbsent(final K key, final Function<K, ValueHolder<V>> source) throws StoreAccessException {
try {
getOrComputeIfAbsentObserver.begin();
Backend<K, V> backEnd = map;
// First try to find the value from heap
OnHeapValueHolder<V> cachedValue = backEnd.get(key);
final long now = timeSource.getTimeMillis();
if (cachedValue == null) {
final Fault<V> fault = new Fault<V>(new NullaryFunction<ValueHolder<V>>() {
@Override
public ValueHolder<V> apply() {
return source.apply(key);
}
});
cachedValue = backEnd.putIfAbsent(key, fault);
if (cachedValue == null) {
return resolveFault(key, backEnd, now, fault);
}
}
// If we have a real value (not a fault), we make sure it is not expired
// If yes, we remove it and ask the source just in case. If no, we return it (below)
if (!(cachedValue instanceof Fault)) {
if (cachedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
expireMappingUnderLock(key, cachedValue);
// On expiration, we might still be able to get a value from the fault. For instance, when a load-writer is used
final Fault<V> fault = new Fault<V>(new NullaryFunction<ValueHolder<V>>() {
@Override
public ValueHolder<V> apply() {
return source.apply(key);
}
});
cachedValue = backEnd.putIfAbsent(key, fault);
if (cachedValue == null) {
return resolveFault(key, backEnd, now, fault);
}
}
else {
setAccessTimeAndExpiryThenReturnMappingOutsideLock(key, cachedValue, now);
}
}
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.HIT);
// Return the value that we found in the cache (by getting the fault or just returning the plain value depending on what we found)
return getValue(cachedValue);
} catch (RuntimeException re) {
handleRuntimeException(re);
return null;
}
}
private ValueHolder<V> resolveFault(final K key, Backend<K, V> backEnd, long now, Fault<V> fault) throws StoreAccessException {
try {
final ValueHolder<V> value = fault.get();
final OnHeapValueHolder<V> newValue;
if(value != null) {
newValue = importValueFromLowerTier(key, value, now, backEnd, fault);
if (newValue == null) {
// Inline expiry or sizing failure
backEnd.remove(key, fault);
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED);
return value;
}
} else {
backEnd.remove(key, fault);
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.MISS);
return null;
}
if (backEnd.replace(key, fault, newValue)) {
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULTED);
updateUsageInBytesIfRequired(newValue.size());
enforceCapacity();
return newValue;
}
final AtomicReference<ValueHolder<V>> invalidatedValue = new AtomicReference<ValueHolder<V>>();
backEnd.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
notifyInvalidation(key, mappedValue);
invalidatedValue.set(mappedValue);
updateUsageInBytesIfRequired(mappedValue.size());
return null;
}
});
ValueHolder<V> p = getValue(invalidatedValue.get());
if (p != null) {
if (p.isExpired(now, TimeUnit.MILLISECONDS)) {
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED_MISS);
return null;
}
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED);
return p;
}
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED);
return newValue;
} catch (Throwable e) {
backEnd.remove(key, fault);
throw new StoreAccessException(e);
}
}
private void invalidateInGetOrComputeIfAbsent(Backend<K, V> map, final K key, final ValueHolder<V> value, final Fault<V> fault, final long now, final Duration expiration) {
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, final OnHeapValueHolder<V> mappedValue) {
if(mappedValue.equals(fault)) {
try {
invalidationListener.onInvalidation(key, cloneValueHolder(key, value, now, expiration, false));
} catch (LimitExceededException ex) {
throw new AssertionError("Sizing is not expected to happen.");
}
return null;
}
return mappedValue;
}
});
}
@Override
public void invalidate(final K key) throws StoreAccessException {
invalidateObserver.begin();
checkKey(key);
try {
final AtomicReference<CachingTierOperationOutcomes.InvalidateOutcome> outcome = new AtomicReference<CachingTierOperationOutcomes.InvalidateOutcome>(CachingTierOperationOutcomes.InvalidateOutcome.MISS);
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(final K k, final OnHeapValueHolder<V> present) {
if (!(present instanceof Fault)) {
notifyInvalidation(key, present);
outcome.set(CachingTierOperationOutcomes.InvalidateOutcome.REMOVED);
}
updateUsageInBytesIfRequired(- present.size());
return null;
}
});
invalidateObserver.end(outcome.get());
} catch (RuntimeException re) {
handleRuntimeException(re);
}
}
@Override
public void silentInvalidate(K key, final Function<Store.ValueHolder<V>, Void> function) throws StoreAccessException {
silentInvalidateObserver.begin();
checkKey(key);
try {
final AtomicReference<HigherCachingTierOperationOutcomes.SilentInvalidateOutcome> outcome =
new AtomicReference<HigherCachingTierOperationOutcomes.SilentInvalidateOutcome>(HigherCachingTierOperationOutcomes.SilentInvalidateOutcome.MISS);
map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
long size = 0L;
OnHeapValueHolder<V> holderToPass = null;
if (mappedValue != null) {
size = mappedValue.size();
if (!(mappedValue instanceof Fault)) {
holderToPass = mappedValue;
outcome.set(HigherCachingTierOperationOutcomes.SilentInvalidateOutcome.REMOVED);
}
}
function.apply(holderToPass);
updateUsageInBytesIfRequired(- size);
return null;
}
});
silentInvalidateObserver.end(outcome.get());
} catch (RuntimeException re) {
handleRuntimeException(re);
}
}
@Override
public void invalidateAll() throws StoreAccessException {
invalidateAllObserver.begin();
long errorCount = 0;
StoreAccessException firstException = null;
for(K key : map.keySet()) {
try {
invalidate(key);
} catch (StoreAccessException cae) {
errorCount++;
if (firstException == null) {
firstException = cae;
}
}
}
if (firstException != null) {
invalidateAllObserver.end(CachingTierOperationOutcomes.InvalidateAllOutcome.FAILURE);
throw new StoreAccessException("Error(s) during invalidation - count is " + errorCount, firstException);
}
clear();
invalidateAllObserver.end(CachingTierOperationOutcomes.InvalidateAllOutcome.SUCCESS);
}
@Override
public void silentInvalidateAll(final BiFunction<K, ValueHolder<V>, Void> biFunction) throws StoreAccessException {
silentInvalidateAllObserver.begin();
StoreAccessException exception = null;
long errorCount = 0;
for (final K k : map.keySet()) {
try {
silentInvalidate(k, new Function<ValueHolder<V>, Void>() {
@Override
public Void apply(ValueHolder<V> mappedValue) {
biFunction.apply(k, mappedValue);
return null;
}
});
} catch (StoreAccessException e) {
errorCount++;
if (exception == null) {
exception = e;
}
}
}
if (exception != null) {
silentInvalidateAllObserver.end(HigherCachingTierOperationOutcomes.SilentInvalidateAllOutcome.FAILURE);
throw new StoreAccessException("silentInvalidateAll failed - error count: " + errorCount, exception);
}
silentInvalidateAllObserver.end(HigherCachingTierOperationOutcomes.SilentInvalidateAllOutcome.SUCCESS);
}
@Override
public void silentInvalidateAllWithHash(long hash, BiFunction<K, ValueHolder<V>, Void> biFunction) throws StoreAccessException {
silentInvalidateAllWithHashObserver.begin();
int intHash = HashUtils.longHashToInt(hash);
Map<K, OnHeapValueHolder<V>> removed = map.removeAllWithHash(intHash);
for (Entry<K, OnHeapValueHolder<V>> entry : removed.entrySet()) {
biFunction.apply(entry.getKey(), entry.getValue());
}
silentInvalidateAllWithHashObserver.end(HigherCachingTierOperationOutcomes.SilentInvalidateAllWithHashOutcome.SUCCESS);
}
private void notifyInvalidation(final K key, final ValueHolder<V> p) {
final InvalidationListener<K, V> invalidationListener = this.invalidationListener;
if(invalidationListener != null) {
invalidationListener.onInvalidation(key, p);
}
}
@Override
public void setInvalidationListener(final InvalidationListener<K, V> providedInvalidationListener) {
this.invalidationListener = new InvalidationListener<K, V>() {
@Override
public void onInvalidation(final K key, final ValueHolder<V> valueHolder) {
if (!(valueHolder instanceof Fault)) {
providedInvalidationListener.onInvalidation(key, valueHolder);
}
}
};
}
@Override
public void invalidateAllWithHash(long hash) throws StoreAccessException {
invalidateAllWithHashObserver.begin();
int intHash = HashUtils.longHashToInt(hash);
Map<K, OnHeapValueHolder<V>> removed = map.removeAllWithHash(intHash);
for (Entry<K, OnHeapValueHolder<V>> entry : removed.entrySet()) {
notifyInvalidation(entry.getKey(), entry.getValue());
}
LOG.debug("CLIENT: onheap store removed all with hash {}", intHash);
invalidateAllWithHashObserver.end(CachingTierOperationOutcomes.InvalidateAllWithHashOutcome.SUCCESS);
}
private ValueHolder<V> getValue(final ValueHolder<V> cachedValue) {
if (cachedValue instanceof Fault) {
return ((Fault<V>)cachedValue).get();
} else {
return cachedValue;
}
}
private long getSizeOfKeyValuePairs(K key, OnHeapValueHolder<V> holder) throws LimitExceededException {
return sizeOfEngine.sizeof(key, holder);
}
/**
* Place holder used when loading an entry from the authority into this caching tier
*
* @param <V> the value type of the caching tier
*/
private static class Fault<V> extends OnHeapValueHolder<V> {
private static final int FAULT_ID = -1;
@IgnoreSizeOf
private final NullaryFunction<ValueHolder<V>> source;
private ValueHolder<V> value;
private Throwable throwable;
private boolean complete;
public Fault(final NullaryFunction<ValueHolder<V>> source) {
super(FAULT_ID, 0, true);
this.source = source;
}
private void complete(ValueHolder<V> value) {
synchronized (this) {
this.value = value;
this.complete = true;
notifyAll();
}
}
private ValueHolder<V> get() {
synchronized (this) {
if (!complete) {
try {
complete(source.apply());
} catch (Throwable e) {
fail(e);
}
}
}
return throwOrReturn();
}
@Override
public long getId() {
throw new UnsupportedOperationException("You should NOT call that?!");
}
private ValueHolder<V> throwOrReturn() {
if (throwable != null) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException("Faulting from repository failed", throwable);
}
return value;
}
private void fail(final Throwable t) {
synchronized (this) {
this.throwable = t;
this.complete = true;
notifyAll();
}
throwOrReturn();
}
@Override
public V value() {
throw new UnsupportedOperationException();
}
@Override
public long creationTime(TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public void setExpirationTime(long expirationTime, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public long expirationTime(TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public boolean isExpired(long expirationTime, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public long lastAccessTime(TimeUnit unit) {
return Long.MAX_VALUE;
}
@Override
public void setLastAccessTime(long lastAccessTime, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public void setSize(long size) {
throw new UnsupportedOperationException("Faults should not be sized");
}
/**
* Faults always have a size of 0
*
* @return {@code 0}
*/
@Override
public long size() {
return 0L;
}
@Override
public String toString() {
return "[Fault : " + (complete ? (throwable == null ? String.valueOf(value) : throwable.getMessage()) : "???") + "]";
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
}
@Override
public ValueHolder<V> compute(final K key, final BiFunction<? super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException {
return compute(key, mappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public ValueHolder<V> compute(final K key, final BiFunction<? super K, ? super V, ? extends V> mappingFunction, final NullaryFunction<Boolean> replaceEqual) throws StoreAccessException {
computeObserver.begin();
checkKey(key);
final long now = timeSource.getTimeMillis();
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
final AtomicReference<OnHeapValueHolder<V>> valueHeld = new AtomicReference<OnHeapValueHolder<V>>();
final AtomicReference<StoreOperationOutcomes.ComputeOutcome> outcome =
new AtomicReference<StoreOperationOutcomes.ComputeOutcome>(StoreOperationOutcomes.ComputeOutcome.MISS);
OnHeapValueHolder<V> computeResult = map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
long sizeDelta = 0L;
if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
sizeDelta -= mappedValue.size();
mappedValue = null;
}
V existingValue = mappedValue == null ? null : mappedValue.value();
V computedValue = mappingFunction.apply(mappedKey, existingValue);
if (computedValue == null) {
if (existingValue != null) {
eventSink.removed(mappedKey, mappedValue);
outcome.set(StoreOperationOutcomes.ComputeOutcome.REMOVED);
updateUsageInBytesIfRequired(- mappedValue.size());
}
return null;
} else if ((eq(existingValue, computedValue)) && (!replaceEqual.apply())) {
if (mappedValue != null) {
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
outcome.set(StoreOperationOutcomes.ComputeOutcome.HIT);
if (holder == null) {
valueHeld.set(mappedValue);
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
checkValue(computedValue);
if (mappedValue != null) {
outcome.set(StoreOperationOutcomes.ComputeOutcome.PUT);
long expirationTime = mappedValue.expirationTime(OnHeapValueHolder.TIME_UNIT);
OnHeapValueHolder<V> valueHolder = newUpdateValueHolder(key, mappedValue, computedValue, now, eventSink);
sizeDelta -= mappedValue.size();
if (valueHolder == null) {
try {
valueHeld.set(makeValue(key, computedValue, now, expirationTime, valueCopier, false));
} catch (LimitExceededException e) {
// Not happening
}
} else {
sizeDelta += valueHolder.size();
}
updateUsageInBytesIfRequired(sizeDelta);
return valueHolder;
} else {
OnHeapValueHolder<V> holder = newCreateValueHolder(key, computedValue, now, eventSink);
if (holder != null) {
outcome.set(StoreOperationOutcomes.ComputeOutcome.PUT);
sizeDelta += holder.size();
}
updateUsageInBytesIfRequired(sizeDelta);
return holder;
}
}
});
if (computeResult == null && valueHeld.get() != null) {
computeResult = valueHeld.get();
}
storeEventDispatcher.releaseEventSink(eventSink);
enforceCapacity();
computeObserver.end(outcome.get());
return computeResult;
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return null;
}
}
@Override
public ValueHolder<V> computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) throws StoreAccessException {
computeIfAbsentObserver.begin();
checkKey(key);
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
final long now = timeSource.getTimeMillis();
final AtomicReference<OnHeapValueHolder<V>> previousValue = new AtomicReference<OnHeapValueHolder<V>>();
final AtomicReference<StoreOperationOutcomes.ComputeIfAbsentOutcome> outcome =
new AtomicReference<StoreOperationOutcomes.ComputeIfAbsentOutcome>(StoreOperationOutcomes.ComputeIfAbsentOutcome.NOOP);
OnHeapValueHolder<V> computeResult = map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
if (mappedValue != null) {
updateUsageInBytesIfRequired(- mappedValue.size());
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
}
V computedValue = mappingFunction.apply(mappedKey);
if (computedValue == null) {
return null;
}
checkValue(computedValue);
OnHeapValueHolder<V> holder = newCreateValueHolder(key, computedValue, now, eventSink);
if (holder != null) {
outcome.set(StoreOperationOutcomes.ComputeIfAbsentOutcome.PUT);
updateUsageInBytesIfRequired(holder.size());
}
return holder;
} else {
previousValue.set(mappedValue);
outcome.set(StoreOperationOutcomes.ComputeIfAbsentOutcome.HIT);
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
if (holder == null) {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
});
OnHeapValueHolder<V> previousValueHolder = previousValue.get();
storeEventDispatcher.releaseEventSink(eventSink);
if (computeResult != null) {
enforceCapacity();
}
computeIfAbsentObserver.end(outcome.get());
if (computeResult == null && previousValueHolder != null) {
// There was a value - it expired on access
return previousValueHolder;
}
return computeResult;
} catch (RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
handleRuntimeException(re);
return null;
}
}
@Override
public Map<K, ValueHolder<V>> bulkComputeIfAbsent(Set<? extends K> keys, final Function<Iterable<? extends K>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> mappingFunction) throws StoreAccessException {
Map<K, ValueHolder<V>> result = new HashMap<K, ValueHolder<V>>();
for (final K key : keys) {
final ValueHolder<V> newValue = computeIfAbsent(key, new Function<K, V>() {
@Override
public V apply(final K k) {
final Iterable<K> keySet = Collections.singleton(k);
final Iterable<? extends Map.Entry<? extends K, ? extends V>> entries = mappingFunction.apply(keySet);
final java.util.Iterator<? extends Map.Entry<? extends K, ? extends V>> iterator = entries.iterator();
final Map.Entry<? extends K, ? extends V> next = iterator.next();
K computedKey = next.getKey();
V computedValue = next.getValue();
checkKey(computedKey);
if (computedValue == null) {
return null;
}
checkValue(computedValue);
return computedValue;
}
});
result.put(key, newValue);
}
return result;
}
@Override
public List<CacheConfigurationChangeListener> getConfigurationChangeListeners() {
List<CacheConfigurationChangeListener> configurationChangeListenerList
= new ArrayList<CacheConfigurationChangeListener>();
configurationChangeListenerList.add(this.cacheConfigurationChangeListener);
return configurationChangeListenerList;
}
@Override
public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, final Function<Iterable<? extends Entry<? extends K, ? extends V>>, Iterable<? extends Entry<? extends K, ? extends V>>> remappingFunction) throws StoreAccessException {
return bulkCompute(keys, remappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, final Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K,? extends V>>> remappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException {
// The Store here is free to slice & dice the keys as it sees fit
// As this OnHeapStore doesn't operate in segments, the best it can do is do a "bulk" write in batches of... one!
Map<K, ValueHolder<V>> result = new HashMap<K, ValueHolder<V>>();
for (K key : keys) {
checkKey(key);
final ValueHolder<V> newValue = compute(key, new BiFunction<K, V, V>() {
@Override
public V apply(final K k, final V oldValue) {
final Set<Map.Entry<K, V>> entrySet = Collections.singletonMap(k, oldValue).entrySet();
final Iterable<? extends Map.Entry<? extends K, ? extends V>> entries = remappingFunction.apply(entrySet);
final java.util.Iterator<? extends Map.Entry<? extends K, ? extends V>> iterator = entries.iterator();
final Map.Entry<? extends K, ? extends V> next = iterator.next();
K key = next.getKey();
V value = next.getValue();
checkKey(key);
if (value != null) {
checkValue(value);
}
return value;
}
}, replaceEqual);
result.put(key, newValue);
}
return result;
}
@Override
public StoreEventSource<K, V> getStoreEventSource() {
return storeEventDispatcher;
}
private OnHeapValueHolder<V> setAccessTimeAndExpiryThenReturnMappingOutsideLock(K key, OnHeapValueHolder<V> valueHolder, long now) {
Duration duration;
try {
duration = expiry.getExpiryForAccess(key, valueHolder);
} catch (RuntimeException re) {
LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
duration = Duration.ZERO;
}
valueHolder.accessed(now, duration);
if (Duration.ZERO.equals(duration)) {
// Expires mapping through computeIfPresent
expireMappingUnderLock(key, valueHolder);
return null;
}
return valueHolder;
}
private OnHeapValueHolder<V> setAccessTimeAndExpiryThenReturnMappingUnderLock(K key, OnHeapValueHolder<V> valueHolder, long now,
StoreEventSink<K, V> eventSink) {
Duration duration = Duration.ZERO;
try {
duration = expiry.getExpiryForAccess(key, valueHolder);
} catch (RuntimeException re) {
LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
}
valueHolder.accessed(now, duration);
if (Duration.ZERO.equals(duration)) {
// Fires event, must happen under lock
fireOnExpirationEvent(key, valueHolder, eventSink);
return null;
}
return valueHolder;
}
private void expireMappingUnderLock(final K key, final ValueHolder<V> value) {
final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
map.computeIfPresent(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, final OnHeapValueHolder<V> mappedValue) {
if(mappedValue.equals(value)) {
fireOnExpirationEvent(key, value, eventSink);
updateUsageInBytesIfRequired(- mappedValue.size());
return null;
}
return mappedValue;
}
});
storeEventDispatcher.releaseEventSink(eventSink);
} catch(RuntimeException re) {
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
throw re;
}
}
private OnHeapValueHolder<V> newUpdateValueHolder(K key, OnHeapValueHolder<V> oldValue, V newValue, long now, StoreEventSink<K, V> eventSink) {
if (oldValue == null) {
throw new NullPointerException();
}
if (newValue == null) {
throw new NullPointerException();
}
Duration duration = Duration.ZERO;
try {
duration = expiry.getExpiryForUpdate(key, oldValue, newValue);
} catch (RuntimeException re) {
LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
}
if (Duration.ZERO.equals(duration)) {
eventSink.updated(key, oldValue, newValue);
eventSink.expired(key, supplierOf(newValue));
return null;
}
long expirationTime;
if (duration == null) {
expirationTime = oldValue.expirationTime(OnHeapValueHolder.TIME_UNIT);
} else {
if (duration.isInfinite()) {
expirationTime = ValueHolder.NO_EXPIRE;
} else {
expirationTime = safeExpireTime(now, duration);
}
}
OnHeapValueHolder<V> holder = null;
try {
holder = makeValue(key, newValue, now, expirationTime, this.valueCopier);
eventSink.updated(key, oldValue, newValue);
} catch (LimitExceededException e) {
LOG.warn(e.getMessage());
eventSink.removed(key, oldValue);
}
return holder;
}
private OnHeapValueHolder<V> newCreateValueHolder(K key, V value, long now, StoreEventSink<K, V> eventSink) {
if (value == null) {
throw new NullPointerException();
}
Duration duration;
try {
duration = expiry.getExpiryForCreation(key, value);
} catch (RuntimeException re) {
LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
return null;
}
if (Duration.ZERO.equals(duration)) {
return null;
}
long expirationTime = duration.isInfinite() ? ValueHolder.NO_EXPIRE : safeExpireTime(now, duration);
OnHeapValueHolder<V> holder = null;
try {
holder = makeValue(key, value, now, expirationTime, this.valueCopier);
eventSink.created(key, value);
} catch (LimitExceededException e) {
LOG.warn(e.getMessage());
}
return holder;
}
private OnHeapValueHolder<V> importValueFromLowerTier(K key, ValueHolder<V> valueHolder, long now, Backend<K, V> backEnd, Fault<V> fault) {
Duration expiration = Duration.ZERO;
try {
expiration = expiry.getExpiryForAccess(key, valueHolder);
} catch (RuntimeException re) {
LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
}
if (Duration.ZERO.equals(expiration)) {
invalidateInGetOrComputeIfAbsent(backEnd, key, valueHolder, fault, now, Duration.ZERO);
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED);
return null;
}
try{
return cloneValueHolder(key, valueHolder, now, expiration, true);
} catch (LimitExceededException e) {
LOG.warn(e.getMessage());
invalidateInGetOrComputeIfAbsent(backEnd, key, valueHolder, fault, now, expiration);
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.FAULT_FAILED);
return null;
}
}
private OnHeapValueHolder<V> cloneValueHolder(K key, ValueHolder<V> valueHolder, long now, Duration expiration, boolean sizingEnabled) throws LimitExceededException {
V realValue = valueHolder.value();
boolean evictionAdvice = checkEvictionAdvice(key, realValue);
OnHeapValueHolder<V> clonedValueHolder = null;
if(valueCopier instanceof SerializingCopier) {
if (valueHolder instanceof BinaryValueHolder && ((BinaryValueHolder) valueHolder).isBinaryValueAvailable()) {
clonedValueHolder = new SerializedOnHeapValueHolder<V>(valueHolder, ((BinaryValueHolder) valueHolder).getBinaryValue(),
evictionAdvice, ((SerializingCopier<V>) valueCopier).getSerializer(), now, expiration);
} else {
clonedValueHolder = new SerializedOnHeapValueHolder<V>(valueHolder, realValue, evictionAdvice,
((SerializingCopier<V>) valueCopier).getSerializer(), now, expiration);
}
} else {
clonedValueHolder = new CopiedOnHeapValueHolder<V>(valueHolder, realValue, evictionAdvice, valueCopier, now, expiration);
}
if (sizingEnabled) {
clonedValueHolder.setSize(getSizeOfKeyValuePairs(key, clonedValueHolder));
}
return clonedValueHolder;
}
private OnHeapValueHolder<V> makeValue(K key, V value, long creationTime, long expirationTime, Copier<V> valueCopier) throws LimitExceededException {
return makeValue(key, value, creationTime, expirationTime, valueCopier, true);
}
private OnHeapValueHolder<V> makeValue(K key, V value, long creationTime, long expirationTime, Copier<V> valueCopier, boolean size) throws LimitExceededException {
boolean evictionAdvice = checkEvictionAdvice(key, value);
OnHeapValueHolder<V> valueHolder;
if (valueCopier instanceof SerializingCopier) {
valueHolder = new SerializedOnHeapValueHolder<V>(value, creationTime, expirationTime, evictionAdvice, ((SerializingCopier<V>) valueCopier).getSerializer());
} else {
valueHolder = new CopiedOnHeapValueHolder<V>(value, creationTime, expirationTime, evictionAdvice, valueCopier);
}
if (size) {
valueHolder.setSize(getSizeOfKeyValuePairs(key, valueHolder));
}
return valueHolder;
}
private boolean checkEvictionAdvice(K key, V value) {
try {
return evictionAdvisor.adviseAgainstEviction(key, value);
} catch (Exception e) {
LOG.error("Exception raised while running eviction advisor " +
"- Eviction will assume entry is NOT advised against eviction", e);
return false;
}
}
private static long safeExpireTime(long now, Duration duration) {
long millis = OnHeapValueHolder.TIME_UNIT.convert(duration.getLength(), duration.getTimeUnit());
if (millis == Long.MAX_VALUE) {
return Long.MAX_VALUE;
}
long result = now + millis;
if (result < 0) {
return Long.MAX_VALUE;
}
return result;
}
private void updateUsageInBytesIfRequired(long delta) {
map.updateUsageInBytesIfRequired(delta);
}
protected long byteSized() {
return map.byteSize();
}
@FindbugsSuppressWarnings("QF_QUESTIONABLE_FOR_LOOP")
protected void enforceCapacity() {
StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
for (int attempts = 0, evicted = 0; attempts < ATTEMPT_RATIO && evicted < EVICTION_RATIO
&& capacity < map.naturalSize(); attempts++) {
if (evict(eventSink)) {
evicted++;
}
}
storeEventDispatcher.releaseEventSink(eventSink);
} catch (RuntimeException re){
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
throw re;
}
}
/**
* Try to evict a mapping.
* @return true if a mapping was evicted, false otherwise.
* @param eventSink target of eviction event
*/
boolean evict(final StoreEventSink<K, V> eventSink) {
evictionObserver.begin();
final Random random = new Random();
@SuppressWarnings("unchecked")
Map.Entry<K, OnHeapValueHolder<V>> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR);
if (candidate == null) {
// 2nd attempt without any advisor
candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice());
}
if (candidate == null) {
return false;
} else {
final Map.Entry<K, OnHeapValueHolder<V>> evictionCandidate = candidate;
final AtomicBoolean removed = new AtomicBoolean(false);
map.computeIfPresent(evictionCandidate.getKey(), new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
if (mappedValue.equals(evictionCandidate.getValue())) {
removed.set(true);
if (!(evictionCandidate.getValue() instanceof Fault)) {
eventSink.evicted(evictionCandidate.getKey(), evictionCandidate.getValue());
invalidationListener.onInvalidation(mappedKey, evictionCandidate.getValue());
}
updateUsageInBytesIfRequired(-mappedValue.size());
return null;
}
return mappedValue;
}
});
if (removed.get()) {
evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.SUCCESS);
return true;
} else {
evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.FAILURE);
return false;
}
}
}
private void checkKey(K keyObject) {
if (keyObject == null) {
throw new NullPointerException();
}
if (!keyType.isAssignableFrom(keyObject.getClass())) {
throw new ClassCastException("Invalid key type, expected : " + keyType.getName() + " but was : " + keyObject.getClass().getName());
}
}
private void checkValue(V valueObject) {
if (valueObject == null) {
throw new NullPointerException();
}
if (!valueType.isAssignableFrom(valueObject.getClass())) {
throw new ClassCastException("Invalid value type, expected : " + valueType.getName() + " but was : " + valueObject.getClass().getName());
}
}
private void fireOnExpirationEvent(K mappedKey, ValueHolder<V> mappedValue, StoreEventSink<K, V> eventSink) {
expirationObserver.begin();
expirationObserver.end(StoreOperationOutcomes.ExpirationOutcome.SUCCESS);
eventSink.expired(mappedKey, mappedValue);
invalidationListener.onInvalidation(mappedKey, mappedValue);
}
private static boolean eq(Object o1, Object o2) {
return (o1 == o2) || (o1 != null && o1.equals(o2));
}
@ServiceDependencies({TimeSourceService.class, CopyProvider.class, SizeOfEngineProvider.class})
public static class Provider implements Store.Provider, CachingTier.Provider, HigherCachingTier.Provider {
private volatile ServiceProvider<Service> serviceProvider;
private final Map<Store<?, ?>, List<Copier>> createdStores = new ConcurrentWeakIdentityHashMap<Store<?, ?>, List<Copier>>();
private final Map<OnHeapStore<?, ?>, Collection<MappedOperationStatistic<?, ?>>> tierOperationStatistics = new ConcurrentWeakIdentityHashMap<OnHeapStore<?, ?>, Collection<MappedOperationStatistic<?, ?>>>();
@Override
public int rank(final Set<ResourceType<?>> resourceTypes, final Collection<ServiceConfiguration<?>> serviceConfigs) {
return resourceTypes.equals(Collections.singleton(ResourceType.Core.HEAP)) ? 1 : 0;
}
@Override
public int rankCachingTier(Set<ResourceType<?>> resourceTypes, Collection<ServiceConfiguration<?>> serviceConfigs) {
return rank(resourceTypes, serviceConfigs);
}
@Override
public <K, V> OnHeapStore<K, V> createStore(final Configuration<K, V> storeConfig, final ServiceConfiguration<?>... serviceConfigs) {
OnHeapStore<K, V> store = createStoreInternal(storeConfig, new ScopedStoreEventDispatcher<K, V>(storeConfig.getDispatcherConcurrency()), serviceConfigs);
Collection<MappedOperationStatistic<?, ?>> tieredOps = new ArrayList<MappedOperationStatistic<?, ?>>();
MappedOperationStatistic<StoreOperationOutcomes.GetOutcome, TierOperationOutcomes.GetOutcome> get =
new MappedOperationStatistic<StoreOperationOutcomes.GetOutcome, TierOperationOutcomes.GetOutcome>(
store, TierOperationOutcomes.GET_TRANSLATION, "get", ResourceType.Core.HEAP.getTierHeight(), "get", STATISTICS_TAG);
StatisticsManager.associate(get).withParent(store);
tieredOps.add(get);
MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome> evict =
new MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome>(
store, TierOperationOutcomes.EVICTION_TRANSLATION, "eviction", ResourceType.Core.HEAP.getTierHeight(), "eviction", STATISTICS_TAG);
StatisticsManager.associate(evict).withParent(store);
tieredOps.add(evict);
tierOperationStatistics.put(store, tieredOps);
return store;
}
public <K, V> OnHeapStore<K, V> createStoreInternal(final Configuration<K, V> storeConfig, final StoreEventDispatcher<K, V> eventDispatcher,
final ServiceConfiguration<?>... serviceConfigs) {
TimeSource timeSource = serviceProvider.getService(TimeSourceService.class).getTimeSource();
CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
Copier<K> keyCopier = copyProvider.createKeyCopier(storeConfig.getKeyType(), storeConfig.getKeySerializer(), serviceConfigs);
Copier<V> valueCopier = copyProvider.createValueCopier(storeConfig.getValueType(), storeConfig.getValueSerializer(), serviceConfigs);
List<Copier> copiers = new ArrayList<Copier>();
copiers.add(keyCopier);
copiers.add(valueCopier);
SizeOfEngineProvider sizeOfEngineProvider = serviceProvider.getService(SizeOfEngineProvider.class);
SizeOfEngine sizeOfEngine = sizeOfEngineProvider.createSizeOfEngine(
storeConfig.getResourcePools().getPoolForResource(ResourceType.Core.HEAP).getUnit(), serviceConfigs);
OnHeapStore<K, V> onHeapStore = new OnHeapStore<K, V>(storeConfig, timeSource, keyCopier, valueCopier, sizeOfEngine, eventDispatcher);
createdStores.put(onHeapStore, copiers);
return onHeapStore;
}
@Override
public void releaseStore(Store<?, ?> resource) {
List<Copier> copiers = createdStores.remove(resource);
if (copiers == null) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
final OnHeapStore onHeapStore = (OnHeapStore)resource;
close(onHeapStore);
StatisticsManager.nodeFor(onHeapStore).clean();
tierOperationStatistics.remove(onHeapStore);
CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
for (Copier copier: copiers) {
try {
copyProvider.releaseCopier(copier);
} catch (Exception e) {
throw new IllegalStateException("Exception while releasing Copier instance.", e);
}
}
}
static void close(final OnHeapStore onHeapStore) {
onHeapStore.clear();
}
@Override
public void initStore(Store<?, ?> resource) {
checkResource(resource);
List<Copier> copiers = createdStores.get(resource);
for (Copier copier : copiers) {
if(copier instanceof SerializingCopier) {
Serializer serializer = ((SerializingCopier)copier).getSerializer();
if(serializer instanceof StatefulSerializer) {
((StatefulSerializer)serializer).init(new TransientStateRepository());
}
}
}
}
private void checkResource(Object resource) {
if (!createdStores.containsKey(resource)) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
}
@Override
public void start(final ServiceProvider<Service> serviceProvider) {
this.serviceProvider = serviceProvider;
}
@Override
public void stop() {
this.serviceProvider = null;
createdStores.clear();
}
@Override
public <K, V> CachingTier<K, V> createCachingTier(Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) {
OnHeapStore<K, V> cachingTier = createStoreInternal(storeConfig, NullStoreEventDispatcher.<K, V>nullStoreEventDispatcher(), serviceConfigs);
Collection<MappedOperationStatistic<?, ?>> tieredOps = new ArrayList<MappedOperationStatistic<?, ?>>();
MappedOperationStatistic<CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome, TierOperationOutcomes.GetOutcome> get =
new MappedOperationStatistic<CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome, TierOperationOutcomes.GetOutcome>(
cachingTier, TierOperationOutcomes.GET_OR_COMPUTEIFABSENT_TRANSLATION, "get", ResourceType.Core.HEAP.getTierHeight(), "getOrComputeIfAbsent", STATISTICS_TAG);
StatisticsManager.associate(get).withParent(cachingTier);
tieredOps.add(get);
MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome> evict
= new MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome>(
cachingTier, TierOperationOutcomes.EVICTION_TRANSLATION, "eviction", ResourceType.Core.HEAP.getTierHeight(), "eviction", STATISTICS_TAG);
StatisticsManager.associate(evict).withParent(cachingTier);
tieredOps.add(evict);
this.tierOperationStatistics.put(cachingTier, tieredOps);
return cachingTier;
}
@Override
public void releaseCachingTier(CachingTier<?, ?> resource) {
checkResource(resource);
try {
resource.invalidateAll();
} catch (StoreAccessException e) {
LOG.warn("Invalidation failure while releasing caching tier", e);
}
releaseStore((Store<?, ?>) resource);
}
@Override
public void initCachingTier(CachingTier<?, ?> resource) {
checkResource(resource);
}
@Override
public <K, V> HigherCachingTier<K, V> createHigherCachingTier(Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) {
OnHeapStore<K, V> higherCachingTier = createStoreInternal(storeConfig, new ScopedStoreEventDispatcher<K, V>(storeConfig.getDispatcherConcurrency()), serviceConfigs);
Collection<MappedOperationStatistic<?, ?>> tieredOps = new ArrayList<MappedOperationStatistic<?, ?>>();
MappedOperationStatistic<CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome, TierOperationOutcomes.GetOutcome> get =
new MappedOperationStatistic<CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome, TierOperationOutcomes.GetOutcome>(
higherCachingTier, TierOperationOutcomes.GET_OR_COMPUTEIFABSENT_TRANSLATION, "get", ResourceType.Core.HEAP.getTierHeight(), "getOrComputeIfAbsent", STATISTICS_TAG);
StatisticsManager.associate(get).withParent(higherCachingTier);
tieredOps.add(get);
MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome> evict =
new MappedOperationStatistic<StoreOperationOutcomes.EvictionOutcome, TierOperationOutcomes.EvictionOutcome>(
higherCachingTier, TierOperationOutcomes.EVICTION_TRANSLATION, "eviction", ResourceType.Core.HEAP.getTierHeight(), "eviction", STATISTICS_TAG);
StatisticsManager.associate(evict).withParent(higherCachingTier);
tieredOps.add(evict);
tierOperationStatistics.put(higherCachingTier, tieredOps);
return higherCachingTier;
}
@Override
public void releaseHigherCachingTier(HigherCachingTier<?, ?> resource) {
releaseCachingTier(resource);
}
@Override
public void initHigherCachingTier(HigherCachingTier<?, ?> resource) {
checkResource(resource);
}
}
}