/* * 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.offheap; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; 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 org.ehcache.Cache; 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.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.core.spi.time.TimeSource; import org.ehcache.impl.internal.store.offheap.factories.EhcacheSegmentFactory; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.events.StoreEventSource; import org.ehcache.core.spi.store.tiering.AuthoritativeTier; import org.ehcache.core.spi.store.tiering.CachingTier; import org.ehcache.core.spi.store.tiering.LowerCachingTier; import org.ehcache.core.statistics.AuthoritativeTierOperationOutcomes; import org.ehcache.core.statistics.LowerCachingTierOperationsOutcome; import org.ehcache.core.statistics.StoreOperationOutcomes; import org.ehcache.impl.internal.store.BinaryValueHolder; import org.ehcache.impl.store.HashUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terracotta.offheapstore.exceptions.OversizeMappingException; import org.terracotta.statistics.StatisticsManager; import org.terracotta.statistics.observer.OperationObserver; import static org.ehcache.core.exceptions.StorePassThroughException.handleRuntimeException; import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf; import static org.terracotta.statistics.StatisticBuilder.operation; public abstract class AbstractOffHeapStore<K, V> implements AuthoritativeTier<K, V>, LowerCachingTier<K, V> { private static final Logger LOG = LoggerFactory.getLogger(AbstractOffHeapStore.class); private static final CachingTier.InvalidationListener<?, ?> NULL_INVALIDATION_LISTENER = new CachingTier.InvalidationListener<Object, Object>() { @Override public void onInvalidation(Object key, ValueHolder<Object> valueHolder) { // Do nothing } }; private final Class<K> keyType; private final Class<V> valueType; private final TimeSource timeSource; private final StoreEventDispatcher<K, V> eventDispatcher; private final Expiry<? super K, ? super V> expiry; private final OperationObserver<StoreOperationOutcomes.GetOutcome> getObserver; private final OperationObserver<StoreOperationOutcomes.PutOutcome> putObserver; private final OperationObserver<StoreOperationOutcomes.PutIfAbsentOutcome> putIfAbsentObserver; private final OperationObserver<StoreOperationOutcomes.RemoveOutcome> removeObserver; 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<AuthoritativeTierOperationOutcomes.GetAndFaultOutcome> getAndFaultObserver; private final OperationObserver<AuthoritativeTierOperationOutcomes.ComputeIfAbsentAndFaultOutcome> computeIfAbsentAndFaultObserver; private final OperationObserver<AuthoritativeTierOperationOutcomes.FlushOutcome> flushObserver; private final OperationObserver<LowerCachingTierOperationsOutcome.InvalidateOutcome> invalidateObserver; private final OperationObserver<LowerCachingTierOperationsOutcome.InvalidateAllOutcome> invalidateAllObserver; private final OperationObserver<LowerCachingTierOperationsOutcome.InvalidateAllWithHashOutcome> invalidateAllWithHashObserver; private final OperationObserver<LowerCachingTierOperationsOutcome.GetAndRemoveOutcome> getAndRemoveObserver; private final OperationObserver<LowerCachingTierOperationsOutcome.InstallMappingOutcome> installMappingObserver; private volatile InvalidationValve valve; protected BackingMapEvictionListener<K, V> mapEvictionListener; @SuppressWarnings("unchecked") private volatile CachingTier.InvalidationListener<K, V> invalidationListener = (CachingTier.InvalidationListener<K, V>) NULL_INVALIDATION_LISTENER; public AbstractOffHeapStore(String statisticsTag, Configuration<K, V> config, TimeSource timeSource, StoreEventDispatcher<K, V> eventDispatcher) { keyType = config.getKeyType(); valueType = config.getValueType(); expiry = config.getExpiry(); this.timeSource = timeSource; this.eventDispatcher = eventDispatcher; this.getObserver = operation(StoreOperationOutcomes.GetOutcome.class).of(this).named("get").tag(statisticsTag).build(); this.putObserver = operation(StoreOperationOutcomes.PutOutcome.class).of(this).named("put").tag(statisticsTag).build(); this.putIfAbsentObserver = operation(StoreOperationOutcomes.PutIfAbsentOutcome.class).of(this).named("putIfAbsent").tag(statisticsTag).build(); this.removeObserver = operation(StoreOperationOutcomes.RemoveOutcome.class).of(this).named("remove").tag(statisticsTag).build(); this.conditionalRemoveObserver = operation(StoreOperationOutcomes.ConditionalRemoveOutcome.class).of(this).named("conditionalRemove").tag(statisticsTag).build(); this.replaceObserver = operation(StoreOperationOutcomes.ReplaceOutcome.class).of(this).named("replace").tag(statisticsTag).build(); this.conditionalReplaceObserver = operation(StoreOperationOutcomes.ConditionalReplaceOutcome.class).of(this).named("conditionalReplace").tag(statisticsTag).build(); this.computeObserver = operation(StoreOperationOutcomes.ComputeOutcome.class).of(this).named("compute").tag(statisticsTag).build(); this.computeIfAbsentObserver = operation(StoreOperationOutcomes.ComputeIfAbsentOutcome.class).of(this).named("computeIfAbsent").tag(statisticsTag).build(); this.evictionObserver = operation(StoreOperationOutcomes.EvictionOutcome.class).of(this).named("eviction").tag(statisticsTag).build(); this.expirationObserver = operation(StoreOperationOutcomes.ExpirationOutcome.class).of(this).named("expiration").tag(statisticsTag).build(); this.getAndFaultObserver = operation(AuthoritativeTierOperationOutcomes.GetAndFaultOutcome.class).of(this).named("getAndFault").tag(statisticsTag).build(); this.computeIfAbsentAndFaultObserver = operation(AuthoritativeTierOperationOutcomes.ComputeIfAbsentAndFaultOutcome.class).of(this).named("computeIfAbsentAndFault").tag(statisticsTag).build(); this.flushObserver = operation(AuthoritativeTierOperationOutcomes.FlushOutcome.class).of(this).named("flush").tag(statisticsTag).build(); this.invalidateObserver = operation(LowerCachingTierOperationsOutcome.InvalidateOutcome.class).of(this).named("invalidate").tag(statisticsTag).build(); this.invalidateAllObserver = operation(LowerCachingTierOperationsOutcome.InvalidateAllOutcome.class).of(this).named("invalidateAll").tag(statisticsTag).build(); this.invalidateAllWithHashObserver = operation(LowerCachingTierOperationsOutcome.InvalidateAllWithHashOutcome.class).of(this).named("invalidateAllWithHash").tag(statisticsTag).build(); this.getAndRemoveObserver= operation(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.class).of(this).named("getAndRemove").tag(statisticsTag).build(); this.installMappingObserver= operation(LowerCachingTierOperationsOutcome.InstallMappingOutcome.class).of(this).named("installMapping").tag(statisticsTag).build(); Set<String> tags = new HashSet<String>(Arrays.asList(statisticsTag, "tier")); StatisticsManager.createPassThroughStatistic(this, "allocatedMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.allocatedMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "occupiedMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.occupiedMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "dataAllocatedMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.dataAllocatedMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "dataOccupiedMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.dataOccupiedMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "dataSize", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.dataSize(); } }); StatisticsManager.createPassThroughStatistic(this, "dataVitalMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.dataVitalMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "mappings", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.longSize(); } }); StatisticsManager.createPassThroughStatistic(this, "maxMappings", tags, new Callable<Number>() { @Override public Number call() throws Exception { return -1L; } }); StatisticsManager.createPassThroughStatistic(this, "vitalMemory", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.vitalMemory(); } }); StatisticsManager.createPassThroughStatistic(this, "removedSlotCount", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.removedSlotCount(); } }); StatisticsManager.createPassThroughStatistic(this, "usedSlotCount", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.usedSlotCount(); } }); StatisticsManager.createPassThroughStatistic(this, "tableCapacity", tags, new Callable<Number>() { @Override public Number call() throws Exception { EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> map = backingMap(); return map == null ? -1L : map.tableCapacity(); } }); this.mapEvictionListener = new BackingMapEvictionListener<K, V>(eventDispatcher, evictionObserver); } @Override public Store.ValueHolder<V> get(K key) throws StoreAccessException { checkKey(key); getObserver.begin(); ValueHolder<V> result = internalGet(key, true, true); if (result == null) { getObserver.end(StoreOperationOutcomes.GetOutcome.MISS); } else { getObserver.end(StoreOperationOutcomes.GetOutcome.HIT); } return result; } private Store.ValueHolder<V> internalGet(K key, final boolean updateAccess, final boolean touchValue) throws StoreAccessException { final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); final AtomicReference<OffHeapValueHolder<V>> heldValue = new AtomicReference<OffHeapValueHolder<V>>(); try { OffHeapValueHolder<V> result = backingMap().computeIfPresent(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { onExpiration(mappedKey, mappedValue, eventSink); return null; } if (updateAccess) { mappedValue.forceDeserialization(); OffHeapValueHolder<V> valueHolder = setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); if (valueHolder == null) { heldValue.set(mappedValue); } return valueHolder; } else if (touchValue) { mappedValue.forceDeserialization(); } return mappedValue; } }); if (result == null && heldValue.get() != null) { result = heldValue.get(); } eventDispatcher.releaseEventSink(eventSink); return result; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); handleRuntimeException(re); return null; } } @Override public boolean containsKey(K key) throws StoreAccessException { checkKey(key); return internalGet(key, false, false) != null; } @Override public PutStatus put(final K key, final V value) throws StoreAccessException { putObserver.begin(); checkKey(key); checkValue(value); final AtomicBoolean added = new AtomicBoolean(); final AtomicReference<OffHeapValueHolder<V>> replacedVal = new AtomicReference<OffHeapValueHolder<V>>(null); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); final long now = timeSource.getTimeMillis(); try { BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> mappingFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { mappedValue = null; } if (mappedValue == null) { OffHeapValueHolder<V> newValue = newCreateValueHolder(key, value, now, eventSink); added.set(newValue != null); return newValue; } else { OffHeapValueHolder<V> newValue = newUpdatedValueHolder(key, value, mappedValue, now, eventSink); replacedVal.set(mappedValue); return newValue; } } }; computeWithRetry(key, mappingFunction, false); eventDispatcher.releaseEventSink(eventSink); if (replacedVal.get() != null) { putObserver.end(StoreOperationOutcomes.PutOutcome.REPLACED); return PutStatus.UPDATE; } else if (added.get()) { putObserver.end(StoreOperationOutcomes.PutOutcome.PUT); return PutStatus.PUT; } else { putObserver.end(StoreOperationOutcomes.PutOutcome.REPLACED); return PutStatus.NOOP; } } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public Store.ValueHolder<V> putIfAbsent(final K key, final V value) throws NullPointerException, StoreAccessException { putIfAbsentObserver.begin(); checkKey(key); checkValue(value); final AtomicReference<Store.ValueHolder<V>> returnValue = new AtomicReference<Store.ValueHolder<V>>(); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); try { BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> mappingFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpiration(mappedKey, mappedValue, eventSink); } return newCreateValueHolder(mappedKey, value, now, eventSink); } mappedValue.forceDeserialization(); returnValue.set(mappedValue); return setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); } }; computeWithRetry(key, mappingFunction, false); eventDispatcher.releaseEventSink(eventSink); ValueHolder<V> resultHolder = returnValue.get(); if (resultHolder == null) { putIfAbsentObserver.end(StoreOperationOutcomes.PutIfAbsentOutcome.PUT); return null; } else { putIfAbsentObserver.end(StoreOperationOutcomes.PutIfAbsentOutcome.HIT); return resultHolder; } } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public boolean remove(final K key) throws StoreAccessException { removeObserver.begin(); checkKey(key); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); final long now = timeSource.getTimeMillis(); final AtomicBoolean removed = new AtomicBoolean(false); try { backingMap().computeIfPresent(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { onExpiration(mappedKey, mappedValue, eventSink); return null; } if (mappedValue != null) { removed.set(true); eventSink.removed(mappedKey, mappedValue); } return null; } }); eventDispatcher.releaseEventSink(eventSink); if (removed.get()) { removeObserver.end(StoreOperationOutcomes.RemoveOutcome.REMOVED); } else { removeObserver.end(StoreOperationOutcomes.RemoveOutcome.MISS); } return removed.get(); } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); handleRuntimeException(re); return false; } } @Override public RemoveStatus remove(final K key, final V value) throws StoreAccessException { conditionalRemoveObserver.begin(); checkKey(key); checkValue(value); final AtomicBoolean removed = new AtomicBoolean(false); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); final AtomicBoolean mappingExists = new AtomicBoolean(); try { backingMap().computeIfPresent(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { onExpiration(mappedKey, mappedValue, eventSink); return null; } else if (mappedValue.value().equals(value)) { removed.set(true); eventSink.removed(mappedKey, mappedValue); return null; } else { mappingExists.set(true); return setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); } } }); eventDispatcher.releaseEventSink(eventSink); if (removed.get()) { conditionalRemoveObserver.end(StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED); return RemoveStatus.REMOVED; } else { conditionalRemoveObserver.end(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS); if (mappingExists.get()) { return RemoveStatus.KEY_PRESENT; } else { return RemoveStatus.KEY_MISSING; } } } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); handleRuntimeException(re); // To please the compiler, the above WILL throw return RemoveStatus.KEY_MISSING; } } @Override public ValueHolder<V> replace(final K key, final V value) throws NullPointerException, StoreAccessException { replaceObserver.begin(); checkKey(key); checkValue(value); final AtomicReference<Store.ValueHolder<V>> returnValue = new AtomicReference<Store.ValueHolder<V>>(null); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> mappingFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpiration(mappedKey, mappedValue, eventSink); } return null; } else { returnValue.set(mappedValue); return newUpdatedValueHolder(mappedKey, value, mappedValue, now, eventSink); } } }; try { computeWithRetry(key, mappingFunction, false); eventDispatcher.releaseEventSink(eventSink); ValueHolder<V> resultHolder = returnValue.get(); if (resultHolder != null) { replaceObserver.end(StoreOperationOutcomes.ReplaceOutcome.REPLACED); } else { replaceObserver.end(StoreOperationOutcomes.ReplaceOutcome.MISS); } return resultHolder; } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public ReplaceStatus replace(final K key, final V oldValue, final V newValue) throws NullPointerException, IllegalArgumentException, StoreAccessException { conditionalReplaceObserver.begin(); checkKey(key); checkValue(oldValue); checkValue(newValue); final AtomicBoolean replaced = new AtomicBoolean(false); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); final AtomicBoolean mappingExists = new AtomicBoolean(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> mappingFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpiration(mappedKey, mappedValue, eventSink); } return null; } else if (oldValue.equals(mappedValue.value())) { replaced.set(true); return newUpdatedValueHolder(mappedKey, newValue, mappedValue, now, eventSink); } else { mappingExists.set(true); return setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); } } }; try { computeWithRetry(key, mappingFunction, false); eventDispatcher.releaseEventSink(eventSink); if (replaced.get()) { conditionalReplaceObserver.end(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED); return ReplaceStatus.HIT; } else { conditionalReplaceObserver.end(StoreOperationOutcomes.ConditionalReplaceOutcome.MISS); if (mappingExists.get()) { return ReplaceStatus.MISS_PRESENT; } else { return ReplaceStatus.MISS_NOT_PRESENT; } } } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public void clear() throws StoreAccessException { try { backingMap().clear(); } catch (RuntimeException re) { handleRuntimeException(re); } } @Override public StoreEventSource<K, V> getStoreEventSource() { return eventDispatcher; } @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, OffHeapValueHolder<V>>> mapIterator = backingMap().entrySet().iterator(); @Override public boolean hasNext() { return mapIterator.hasNext(); } @Override public Cache.Entry<K, ValueHolder<V>> next() throws StoreAccessException { Map.Entry<K, OffHeapValueHolder<V>> next = mapIterator.next(); final K key = next.getKey(); final OffHeapValueHolder<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> compute(K key, 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 AtomicBoolean write = new AtomicBoolean(false); final AtomicReference<OffHeapValueHolder<V>> valueHeld = new AtomicReference<OffHeapValueHolder<V>>(); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> computeFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); V existingValue = null; if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpiration(mappedKey, mappedValue, eventSink); } mappedValue = null; } else { existingValue = mappedValue.value(); } V computedValue = mappingFunction.apply(mappedKey, existingValue); if (computedValue == null) { if (mappedValue != null) { write.set(true); eventSink.removed(mappedKey, mappedValue); } return null; } else if (safeEquals(existingValue, computedValue) && !replaceEqual.apply()) { if (mappedValue != null) { OffHeapValueHolder<V> valueHolder = setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); if (valueHolder == null) { valueHeld.set(mappedValue); } return valueHolder; } else { return null; } } checkValue(computedValue); write.set(true); if (mappedValue != null) { OffHeapValueHolder<V> valueHolder = newUpdatedValueHolder(key, computedValue, mappedValue, now, eventSink); if (valueHolder == null) { valueHeld.set(new BasicOffHeapValueHolder<V>(mappedValue.getId(), computedValue, now, now)); } return valueHolder; } else { return newCreateValueHolder(key, computedValue, now, eventSink); } } }; OffHeapValueHolder<V> result; try { result = computeWithRetry(key, computeFunction, false); if (result == null && valueHeld.get() != null) { result = valueHeld.get(); } eventDispatcher.releaseEventSink(eventSink); if (result == null) { if (write.get()) { computeObserver.end(StoreOperationOutcomes.ComputeOutcome.REMOVED); } else { computeObserver.end(StoreOperationOutcomes.ComputeOutcome.MISS); } } else if (write.get()) { computeObserver.end(StoreOperationOutcomes.ComputeOutcome.PUT); } else { computeObserver.end(StoreOperationOutcomes.ComputeOutcome.HIT); } return result; } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public ValueHolder<V> computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) throws StoreAccessException { return internalComputeIfAbsent(key, mappingFunction, false, false); } private Store.ValueHolder<V> internalComputeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction, boolean fault, final boolean delayedDeserialization) throws StoreAccessException { if (fault) { computeIfAbsentAndFaultObserver.begin(); } else { computeIfAbsentObserver.begin(); } checkKey(key); final AtomicBoolean write = new AtomicBoolean(false); final AtomicReference<OffHeapValueHolder<V>> valueHeld = new AtomicReference<OffHeapValueHolder<V>>(); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> computeFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpiration(mappedKey, mappedValue, eventSink); } write.set(true); V computedValue = mappingFunction.apply(mappedKey); if (computedValue == null) { return null; } else { checkValue(computedValue); return newCreateValueHolder(mappedKey, computedValue, now, eventSink); } } else { OffHeapValueHolder<V> valueHolder = setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink); if (valueHolder != null) { if (delayedDeserialization) { mappedValue.detach(); } else { mappedValue.forceDeserialization(); } } else { valueHeld.set(mappedValue); } return valueHolder; } } }; OffHeapValueHolder<V> computeResult; try { computeResult = computeWithRetry(key, computeFunction, fault); if (computeResult == null && valueHeld.get() != null) { computeResult = valueHeld.get(); } eventDispatcher.releaseEventSink(eventSink); if (write.get()) { if (computeResult != null) { if (fault) { computeIfAbsentAndFaultObserver.end(AuthoritativeTierOperationOutcomes.ComputeIfAbsentAndFaultOutcome.PUT); } else { computeIfAbsentObserver.end(StoreOperationOutcomes.ComputeIfAbsentOutcome.PUT); } } else { if (fault) { computeIfAbsentAndFaultObserver.end(AuthoritativeTierOperationOutcomes.ComputeIfAbsentAndFaultOutcome.NOOP); } else { computeIfAbsentObserver.end(StoreOperationOutcomes.ComputeIfAbsentOutcome.NOOP); } } } else { if (fault) { computeIfAbsentAndFaultObserver.end(AuthoritativeTierOperationOutcomes.ComputeIfAbsentAndFaultOutcome.HIT); } else { computeIfAbsentObserver.end(StoreOperationOutcomes.ComputeIfAbsentOutcome.HIT); } } return computeResult; } catch (StoreAccessException caex) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, caex); throw caex; } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.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 { Map<K, ValueHolder<V>> result = new HashMap<K, ValueHolder<V>>(); for (K key : keys) { checkKey(key); BiFunction<K, V, V> biFunction = new BiFunction<K, V, V>() { @Override public V apply(final K k, final V v) { Map.Entry<K, V> entry = new Map.Entry<K, V>() { @Override public K getKey() { return k; } @Override public V getValue() { return v; } @Override public V setValue(V value) { throw new UnsupportedOperationException(); } }; java.util.Iterator<? extends Map.Entry<? extends K, ? extends V>> iterator = remappingFunction.apply(Collections .singleton(entry)).iterator(); Map.Entry<? extends K, ? extends V> result = iterator.next(); if (result != null) { checkKey(result.getKey()); return result.getValue(); } else { return null; } } }; ValueHolder<V> computed = compute(key, biFunction, replaceEqual); result.put(key, computed); } return result; } @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 (K key : keys) { checkKey(key); Function<K, V> function = new Function<K, V>() { @Override public V apply(K k) { java.util.Iterator<? extends Map.Entry<? extends K, ? extends V>> iterator = mappingFunction.apply(Collections.singleton(k)).iterator(); Map.Entry<? extends K, ? extends V> result = iterator.next(); if (result != null) { checkKey(result.getKey()); return result.getValue(); } else { return null; } } }; ValueHolder<V> computed = computeIfAbsent(key, function); result.put(key, computed); } return result; } @Override public ValueHolder<V> getAndFault(K key) throws StoreAccessException { getAndFaultObserver.begin(); checkKey(key); ValueHolder<V> mappedValue = null; final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); try { mappedValue = backingMap().computeIfPresentAndPin(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { if(mappedValue.isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS)) { onExpiration(mappedKey, mappedValue, eventSink); return null; } mappedValue.detach(); return mappedValue; } }); eventDispatcher.releaseEventSink(eventSink); if (mappedValue == null) { getAndFaultObserver.end(AuthoritativeTierOperationOutcomes.GetAndFaultOutcome.MISS); } else { getAndFaultObserver.end(AuthoritativeTierOperationOutcomes.GetAndFaultOutcome.HIT); } } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); handleRuntimeException(re); } return mappedValue; } @Override public ValueHolder<V> computeIfAbsentAndFault(K key, Function<? super K, ? extends V> mappingFunction) throws StoreAccessException { return internalComputeIfAbsent(key, mappingFunction, true, true); } @Override public boolean flush(K key, final ValueHolder<V> valueFlushed) { flushObserver.begin(); checkKey(key); final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); try { boolean result = backingMap().computeIfPinned(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K k, OffHeapValueHolder<V> valuePresent) { if (valuePresent.getId() == valueFlushed.getId()) { if (valueFlushed.isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS)) { onExpiration(k, valuePresent, eventSink); return null; } valuePresent.updateMetadata(valueFlushed); valuePresent.writeBack(); } return valuePresent; } }, new Function<OffHeapValueHolder<V>, Boolean>() { @Override public Boolean apply(OffHeapValueHolder<V> valuePresent) { return valuePresent.getId() == valueFlushed.getId(); } }); eventDispatcher.releaseEventSink(eventSink); if (result) { flushObserver.end(AuthoritativeTierOperationOutcomes.FlushOutcome.HIT); return true; } else { flushObserver.end(AuthoritativeTierOperationOutcomes.FlushOutcome.MISS); return false; } } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); throw re; } } @Override public void setInvalidationValve(InvalidationValve valve) { this.valve = valve; } @Override public void setInvalidationListener(CachingTier.InvalidationListener<K, V> invalidationListener) { this.invalidationListener = invalidationListener; mapEvictionListener.setInvalidationListener(invalidationListener); } @Override public void invalidate(final K key) throws StoreAccessException { invalidateObserver.begin(); final AtomicBoolean removed = new AtomicBoolean(false); try { backingMap().computeIfPresent(key, new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(final K k, final OffHeapValueHolder<V> present) { removed.set(true); notifyInvalidation(key, present); return null; } }); if (removed.get()) { invalidateObserver.end(LowerCachingTierOperationsOutcome.InvalidateOutcome.REMOVED); } else { invalidateObserver.end(LowerCachingTierOperationsOutcome.InvalidateOutcome.MISS); } } catch (RuntimeException re) { handleRuntimeException(re); } } @Override public void invalidateAll() throws StoreAccessException { invalidateAllObserver.begin(); StoreAccessException exception = null; long errorCount = 0; for (K k : backingMap().keySet()) { try { invalidate(k); } catch (StoreAccessException e) { errorCount++; if (exception == null) { exception = e; } } } if (exception != null) { invalidateAllObserver.end(LowerCachingTierOperationsOutcome.InvalidateAllOutcome.FAILURE); throw new StoreAccessException("invalidateAll failed - error count: " + errorCount, exception); } invalidateAllObserver.end(LowerCachingTierOperationsOutcome.InvalidateAllOutcome.SUCCESS); } @Override public void invalidateAllWithHash(long hash) { invalidateAllWithHashObserver.begin(); int intHash = HashUtils.longHashToInt(hash); Map<K, OffHeapValueHolder<V>> removed = backingMap().removeAllWithHash(intHash); for (Map.Entry<K, OffHeapValueHolder<V>> entry : removed.entrySet()) { notifyInvalidation(entry.getKey(), entry.getValue()); } invalidateAllWithHashObserver.end(LowerCachingTierOperationsOutcome.InvalidateAllWithHashOutcome.SUCCESS); } private void notifyInvalidation(final K key, final ValueHolder<V> p) { final CachingTier.InvalidationListener<K, V> invalidationListener = this.invalidationListener; if (invalidationListener != null) { invalidationListener.onInvalidation(key, p); } } /** * {@inheritDoc} * Note that this implementation is atomic. */ @Override public ValueHolder<V> getAndRemove(final K key) throws StoreAccessException { getAndRemoveObserver.begin(); checkKey(key); final AtomicReference<ValueHolder<V>> valueHolderAtomicReference = new AtomicReference<ValueHolder<V>>(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> computeFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K mappedKey, OffHeapValueHolder<V> mappedValue) { long now = timeSource.getTimeMillis(); if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { if (mappedValue != null) { onExpirationInCachingTier(mappedValue, key); } return null; } mappedValue.detach(); valueHolderAtomicReference.set(mappedValue); return null; } }; try { backingMap().compute(key, computeFunction, false); ValueHolder<V> result = valueHolderAtomicReference.get(); if (result == null) { getAndRemoveObserver.end(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.MISS); } else { getAndRemoveObserver.end(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.HIT_REMOVED); } return result; } catch (RuntimeException re) { handleRuntimeException(re); return null; } } @Override public ValueHolder<V> installMapping(final K key, final Function<K, ValueHolder<V>> source) throws StoreAccessException { installMappingObserver.begin(); BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> computeFunction = new BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>>() { @Override public OffHeapValueHolder<V> apply(K k, OffHeapValueHolder<V> offHeapValueHolder) { if (offHeapValueHolder != null) { throw new AssertionError(); } ValueHolder<V> valueHolder = source.apply(k); if (valueHolder != null) { if (valueHolder.isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS)) { onExpirationInCachingTier(valueHolder, key); return null; } else { return newTransferValueHolder(valueHolder); } } return null; } }; OffHeapValueHolder<V> computeResult; try { computeResult = computeWithRetry(key, computeFunction, false); if (computeResult != null) { installMappingObserver.end(LowerCachingTierOperationsOutcome.InstallMappingOutcome.PUT); } else { installMappingObserver.end(LowerCachingTierOperationsOutcome.InstallMappingOutcome.NOOP); } return computeResult; } catch (RuntimeException re) { handleRuntimeException(re); return null; } } private OffHeapValueHolder<V> computeWithRetry(K key, BiFunction<K, OffHeapValueHolder<V>, OffHeapValueHolder<V>> computeFunction, boolean fault) throws StoreAccessException { OffHeapValueHolder<V> computeResult = null; try { computeResult = backingMap().compute(key, computeFunction, fault); } catch (OversizeMappingException ex) { try { evictionAdvisor().setSwitchedOn(false); invokeValve(); computeResult = backingMap().compute(key, computeFunction, fault); } catch (OversizeMappingException e) { throw new StoreAccessException("The element with key '" + key + "' is too large to be stored" + " in this offheap store.", e); } catch (RuntimeException e) { handleRuntimeException(e); } finally { evictionAdvisor().setSwitchedOn(true); } } catch (RuntimeException re) { handleRuntimeException(re); } return computeResult; } private boolean safeEquals(V existingValue, V computedValue) { return existingValue == computedValue || (existingValue != null && existingValue.equals(computedValue)); } private static final NullaryFunction<Boolean> REPLACE_EQUALS_TRUE = new NullaryFunction<Boolean>() { @Override public Boolean apply() { return Boolean.TRUE; } }; private OffHeapValueHolder<V> setAccessTimeAndExpiryThenReturnMapping(K key, OffHeapValueHolder<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); } if (Duration.ZERO.equals(duration)) { onExpiration(key, valueHolder, eventSink); return null; } valueHolder.accessed(now, duration); valueHolder.writeBack(); return valueHolder; } private OffHeapValueHolder<V> newUpdatedValueHolder(K key, V value, OffHeapValueHolder<V> existing, long now, StoreEventSink<K, V> eventSink) { eventSink.updated(key, existing, value); Duration duration = Duration.ZERO; try { duration = expiry.getExpiryForUpdate(key, existing, value); } catch (RuntimeException re) { LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re); } if (Duration.ZERO.equals(duration)) { eventSink.expired(key, supplierOf(value)); return null; } if (duration == null) { return new BasicOffHeapValueHolder<V>(backingMap().nextIdFor(key), value, now, existing.expirationTime(OffHeapValueHolder.TIME_UNIT)); } else if (duration.isInfinite()) { return new BasicOffHeapValueHolder<V>(backingMap().nextIdFor(key), value, now, OffHeapValueHolder.NO_EXPIRE); } else { return new BasicOffHeapValueHolder<V>(backingMap().nextIdFor(key), value, now, safeExpireTime(now, duration)); } } private OffHeapValueHolder<V> newCreateValueHolder(K key, V value, long now, StoreEventSink<K, V> eventSink) { Duration duration = Duration.ZERO; try { duration = expiry.getExpiryForCreation(key, value); } catch (RuntimeException re) { LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", re); } if (Duration.ZERO.equals(duration)) { return null; } eventSink.created(key, value); if (duration.isInfinite()) { return new BasicOffHeapValueHolder<V>(backingMap().nextIdFor(key), value, now, OffHeapValueHolder.NO_EXPIRE); } else { return new BasicOffHeapValueHolder<V>(backingMap().nextIdFor(key), value, now, safeExpireTime(now, duration)); } } private OffHeapValueHolder<V> newTransferValueHolder(ValueHolder<V> valueHolder) { if (valueHolder instanceof BinaryValueHolder && ((BinaryValueHolder) valueHolder).isBinaryValueAvailable()) { return new BinaryOffHeapValueHolder<V>(valueHolder.getId(), valueHolder.value(), ((BinaryValueHolder)valueHolder).getBinaryValue(), valueHolder.creationTime(OffHeapValueHolder.TIME_UNIT), valueHolder.expirationTime(OffHeapValueHolder.TIME_UNIT), valueHolder.lastAccessTime(OffHeapValueHolder.TIME_UNIT), valueHolder.hits()); } else { return new BasicOffHeapValueHolder<V>(valueHolder.getId(), valueHolder.value(), valueHolder.creationTime(OffHeapValueHolder.TIME_UNIT), valueHolder.expirationTime(OffHeapValueHolder.TIME_UNIT), valueHolder.lastAccessTime(OffHeapValueHolder.TIME_UNIT), valueHolder .hits()); } } private void invokeValve() throws StoreAccessException { InvalidationValve valve = this.valve; if (valve != null) { valve.invalidateAll(); } } private static long safeExpireTime(long now, Duration duration) { long millis = OffHeapValueHolder.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 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 onExpirationInCachingTier(ValueHolder<V> mappedValue, K key) { expirationObserver.begin(); invalidationListener.onInvalidation(key, mappedValue); expirationObserver.end(StoreOperationOutcomes.ExpirationOutcome.SUCCESS); } private void onExpiration(K mappedKey, ValueHolder<V> mappedValue, StoreEventSink<K, V> eventSink) { expirationObserver.begin(); eventSink.expired(mappedKey, mappedValue); invalidationListener.onInvalidation(mappedKey, mappedValue); expirationObserver.end(StoreOperationOutcomes.ExpirationOutcome.SUCCESS); } /** * Note to users of this method: this method can return null if called * after the tier was "closed" (i.e. by passthrough stats) */ protected abstract EhcacheOffHeapBackingMap<K, OffHeapValueHolder<V>> backingMap(); protected abstract SwitchableEvictionAdvisor<K, OffHeapValueHolder<V>> evictionAdvisor(); protected static <K, V> SwitchableEvictionAdvisor<K, OffHeapValueHolder<V>> wrap(EvictionAdvisor<? super K, ? super V> delegate) { return new OffHeapEvictionAdvisorWrapper<K, V>(delegate); } private static class OffHeapEvictionAdvisorWrapper<K, V> implements SwitchableEvictionAdvisor<K, OffHeapValueHolder<V>> { private final EvictionAdvisor<? super K, ? super V> delegate; private volatile boolean adviceEnabled; private OffHeapEvictionAdvisorWrapper(EvictionAdvisor<? super K, ? super V> delegate) { this.delegate = delegate; } @Override public boolean adviseAgainstEviction(K key, OffHeapValueHolder<V> value) { try { return delegate.adviseAgainstEviction(key, value.value()); } catch (Exception e) { LOG.error("Exception raised while running eviction advisor " + "- Eviction will assume entry is NOT advised against eviction", e); return false; } } @Override public boolean isSwitchedOn() { return adviceEnabled; } @Override public void setSwitchedOn(boolean switchedOn) { this.adviceEnabled = switchedOn; } } static class BackingMapEvictionListener<K, V> implements EhcacheSegmentFactory.EhcacheSegment.EvictionListener<K, OffHeapValueHolder<V>> { private final StoreEventDispatcher<K, V> eventDispatcher; private final OperationObserver<StoreOperationOutcomes.EvictionOutcome> evictionObserver; private volatile CachingTier.InvalidationListener<K, V> invalidationListener; private BackingMapEvictionListener(StoreEventDispatcher<K, V> eventDispatcher, OperationObserver<StoreOperationOutcomes.EvictionOutcome> evictionObserver) { this.eventDispatcher = eventDispatcher; this.evictionObserver = evictionObserver; @SuppressWarnings("unchecked") CachingTier.InvalidationListener<K, V> nullInvalidationListener = (CachingTier.InvalidationListener<K, V>) NULL_INVALIDATION_LISTENER; this.invalidationListener = nullInvalidationListener; } public void setInvalidationListener(CachingTier.InvalidationListener<K, V> invalidationListener) { if (invalidationListener == null) { throw new NullPointerException("invalidation listener cannot be null"); } this.invalidationListener = invalidationListener; } @Override public void onEviction(K key, OffHeapValueHolder<V> value) { evictionObserver.begin(); StoreEventSink<K, V> eventSink = eventDispatcher.eventSink(); try { eventSink.evicted(key, value); eventDispatcher.releaseEventSink(eventSink); } catch (RuntimeException re) { eventDispatcher.releaseEventSinkAfterFailure(eventSink, re); } invalidationListener.onInvalidation(key, value); evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.SUCCESS); } } }