All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.net.cache.SerializationCache Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2023, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */

package com.tangosol.net.cache;


import com.tangosol.io.BinaryStore;

import com.tangosol.util.AbstractKeyBasedMap;
import com.tangosol.util.AbstractKeySetBasedMap;
import com.tangosol.util.Base;
import com.tangosol.util.Binary;
import com.tangosol.util.LiteSet;
import com.tangosol.util.LongArray;
import com.tangosol.util.MapEvent;
import com.tangosol.util.SparseArray;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**
* A version of SerializationMap that implements an LRU policy.
* 

* This implementation is partially thread safe. It assumes that multiple * threads will not be accessing the same keys at the same time, nor would * any other thread be accessing this cache while a clear() operation were * going on, for example. In other words, this implementation assumes that * access to this cache is either single-threaded or gated through an object * like WrapperConcurrentMap. *

* The primary reason why SerializationCache is a sub-class of * SerializationMap instead of combining their functionality is that * SerializationMap represents a passive store, and thus does not implement * the ObservableMap interface, while SerializationCache represents an * active store, and thus clients of it would have to always handle potential * events, even if it were not size-limited. * * @since Coherence 2.2 * @author cp 2003.05.28 */ public class SerializationCache extends AbstractSerializationCache implements CacheMap, ConfigurableCacheMap { // ----- constructors --------------------------------------------------- /** * Construct a SerializationCache on top of a BinaryStore. * * @param store the BinaryStore to use to write the serialized objects to * @param cMax the maximum number of items to store in the binary store */ public SerializationCache(BinaryStore store, int cMax) { this(store, cMax, null); } /** * Construct a SerializationCache on top of a BinaryStore. * * @param store the BinaryStore to use to write the serialized objects to * @param cMax the maximum number of items to store in the binary store * @param loader the ClassLoader to use for deserialization */ public SerializationCache(BinaryStore store, int cMax, ClassLoader loader) { super(store, loader); setHighUnits(cMax); } /** * Construct a SerializationCache on top of a BinaryStore. * * @param store the BinaryStore to use to write the serialized objects to * @param cMax the maximum number of items to store in the binary store * @param fBinaryMap true indicates that this map will only manage * binary keys and values * @since Coherence 2.4 */ public SerializationCache(BinaryStore store, int cMax, boolean fBinaryMap) { super(store, fBinaryMap); setHighUnits(cMax); } // ----- Map interface -------------------------------------------------- /** * {@inheritDoc} */ public synchronized void clear() { try { super.clear(); } finally { getLruArray().clear(); getExpiryArray().clear(); m_cCurUnits = 0L; } } /** * {@inheritDoc} */ public boolean containsKey(Object oKey) { checkExpiry(); boolean fContains = super.containsKey(oKey); if (fContains) { touch(oKey); } return fContains; } /** * {@inheritDoc} */ public boolean containsValue(Object oValue) { checkExpiry(); return super.containsValue(oValue); } /** * {@inheritDoc} */ public Object get(Object oKey) { checkExpiry(); touch(oKey); return super.get(oKey); } /** * {@inheritDoc} */ public boolean isEmpty() { checkExpiry(); return super.isEmpty(); } /** * {@inheritDoc} */ public Object put(Object oKey, Object oValue) { checkExpiry(); Object oOrig = super.put(oKey, oValue); checkSize(); return oOrig; } /** * {@inheritDoc} */ public void putAll(Map map) { checkExpiry(); super.putAll(map); checkSize(); } /** * {@inheritDoc} */ public Object remove(Object oKey) { checkExpiry(); return super.remove(oKey); } /** * {@inheritDoc} */ public int size() { checkExpiry(); return super.size(); } // ----- AbstractKeySetBasedMap methods --------------------------------- /** * {@inheritDoc} */ protected boolean removeBlind(Object oKey) { checkExpiry(); return super.removeBlind(oKey); } // ----- CacheMap interface --------------------------------------------- /** * {@inheritDoc} */ public Object put(Object oKey, Object oValue, long cMillis) { if (cMillis > Integer.MAX_VALUE) { throw new IllegalArgumentException("TTL value (" + cMillis + ") is out of range (0.." + Integer.MAX_VALUE + ")"); } Object oOrig = put(oKey, oValue); // if necessary change the expiry for the entry if (cMillis != EXPIRY_DEFAULT && cMillis != getExpiryDelay()) { registerKey(oKey, null, null, (int) cMillis); } return oOrig; } // ----- ConfigurableCacheMap interface --------------------------------- /** * {@inheritDoc} */ public int getUnits() { if (m_calculator == null) { return super.size(); } return LocalCache.toExternalUnits(m_cCurUnits, getUnitFactor()); } /** * {@inheritDoc} */ public int getHighUnits() { return LocalCache.toExternalUnits(m_cMaxUnits, getUnitFactor()); } /** * {@inheritDoc} */ public void setHighUnits(int cMax) { long cUnits = LocalCache.toInternalUnits(cMax, getUnitFactor()); m_cMaxUnits = cUnits; m_cPruneUnits = cUnits == Long.MAX_VALUE ? cUnits : (long) (LocalCache.DEFAULT_PRUNE * cUnits); checkSize(); } /** * {@inheritDoc} */ public int getLowUnits() { return LocalCache.toExternalUnits(m_cPruneUnits, getUnitFactor()); } /** * {@inheritDoc} */ public void setLowUnits(int cUnits) { m_cPruneUnits = LocalCache.toInternalUnits(cUnits, getUnitFactor()); } /** * {@inheritDoc} */ public int getUnitFactor() { return m_nUnitFactor; } /** * {@inheritDoc} */ public void setUnitFactor(int nFactor) { if (nFactor == m_nUnitFactor) { return; } if (nFactor < 1) { throw new IllegalArgumentException("unit factor must be >= 1"); } if (m_cCurUnits > 0) { throw new IllegalStateException( "unit factor cannot be set after the cache has been populated"); } // only adjust the max units if there was no unit factor set previously if (m_nUnitFactor == 1 && m_cMaxUnits != Long.MAX_VALUE) { m_cMaxUnits *= nFactor; } m_nUnitFactor = nFactor; } /** * {@inheritDoc} */ public EvictionPolicy getEvictionPolicy() { return InternalEvictionPolicy.INSTANCE; } /** * {@inheritDoc} */ public void setEvictionPolicy(EvictionPolicy policy) { if (policy != null && policy != InternalEvictionPolicy.INSTANCE) { throw new IllegalArgumentException("unsupported eviction policy"); } } /** * {@inheritDoc} */ public void evict(Object oKey) { ConfigurableCacheMap.EvictionApprover apprvr = m_apprvrEvict; if ((apprvr == null || apprvr.isEvictable(getCacheEntry(oKey))) && getKeyMap().containsKey(oKey)) { if (hasListeners()) { dispatchPendingEvent(oKey, MapEvent.ENTRY_DELETED, null, true); } getBinaryStore().erase(toBinary(oKey)); unregisterKey(oKey); } } /** * {@inheritDoc} */ public void evictAll(Collection colKeys) { for (Iterator iter = colKeys.iterator(); iter.hasNext(); ) { Object oKey = iter.next(); evict(oKey); } } /** * {@inheritDoc} */ public void evict() { checkExpiry(); checkSize(); } /** * {@inheritDoc} */ public int getExpiryDelay() { return m_cDefaultTTLMillis; } /** * {@inheritDoc} */ public synchronized void setExpiryDelay(int cMillis) { if (cMillis != m_cDefaultTTLMillis) { if (cMillis < 0) { cMillis = 0; } m_cDefaultTTLMillis = cMillis; } } /** * {@inheritDoc} */ public long getNextExpiryTime() { LongArray arrayExpiry = getExpiryArray(); return arrayExpiry.isEmpty() ? 0 : arrayExpiry.getFirstIndex(); } /** * {@inheritDoc} */ public EvictionApprover getEvictionApprover() { return m_apprvrEvict; } /** * {@inheritDoc} */ public void setEvictionApprover(EvictionApprover approver) { m_apprvrEvict = approver; } /** * {@inheritDoc} */ public ConfigurableCacheMap.Entry getCacheEntry(Object oKey) { return super.containsKey(oKey) ? (ConfigurableCacheMap.Entry) ((EntrySet) entrySet()).instantiateEntry(oKey, null) : null; } /** * {@inheritDoc} */ public UnitCalculator getUnitCalculator() { UnitCalculator calculator = m_calculator; return calculator == null ? LocalCache.InternalUnitCalculator.INSTANCE : calculator; } /** * {@inheritDoc} */ public synchronized void setUnitCalculator(UnitCalculator calculator) { if (calculator == LocalCache.InternalUnitCalculator.INSTANCE) { calculator = null; } if (calculator != m_calculator) { // recalculate the current number of units if (calculator == null) { // use the "fixed" calculator, i.e. each entry is 1 unit m_cCurUnits = size(); } else { // get a stable view of the current set of keys Map mapAttr = getKeyMap(); Object[] aoKey = mapAttr.keySet().toArray(); // recalc all of the per-entry unit figures and the total int cTotal = 0; BinaryStore store = getBinaryStore(); for (int i = 0, c = aoKey.length; i < c; ++i) { Object oKey = aoKey[i]; EntryAttributes attr = (EntryAttributes) mapAttr.get(oKey); Binary binKey = toBinary(oKey); Binary binValue = store.load(binKey); int cUnits = calculator.calculateUnits(binKey, binValue); // update the entry mapAttr.put(oKey, instantiateEntryAttributes(attr, attr.getExpiryTime(), attr.getTouchCount(), cUnits)); // update the total cTotal += cUnits; } m_cCurUnits = cTotal; } m_calculator = calculator; } } // ----- SerializationCache methods ------------------------------------- /** * Flush items that have expired. * * @deprecated use {@link #evict()} */ public void flush() { checkExpiry(); } // ----- Object methods ------------------------------------------------- /** * {@inheritDoc} */ public String toString() { return "SerializationCache {" + getDescription() + "}"; } // ----- accessors ------------------------------------------------------ /** * {@inheritDoc} */ protected synchronized void setBinaryStore(BinaryStore store) { BinaryStore storeOrig = getBinaryStore(); if (store != storeOrig) { getLruArray().clear(); getExpiryArray().clear(); m_cCurUnits = 0L; super.setBinaryStore(store); } } /** * Determine the next value from the touch counter. * * @return the next value from the touch counter */ protected long getTouchCounter() { long c = m_cTouches + 1; m_cTouches = c; return c; } /** * Get the LRU data structure. The key is a touch count, the value is the * Object key for the cache. * * @return the the LRU data structure */ protected LongArray getLruArray() { LongArray array = m_arrayLRU; if (array == null) { m_arrayLRU = array = new SparseArray(); } return array; } /** * Get the Expiry data structure. The key is a date/time value, the value * is a set of Object keys for the cache that expire at that time. * * @return the the Expiry data structure */ protected LongArray getExpiryArray() { LongArray array = m_arrayExpiry; if (array == null) { m_arrayExpiry = array = new SparseArray(); } return array; } /** * {@inheritDoc} */ protected String getDescription() { return super.getDescription() + ", Units=" + getUnits() + ", HighUnits=" + getHighUnits() + ", UnitFactor=" + getUnitFactor() + ", EvictionPolicy=" + getEvictionPolicy().getName() + ", UnitCalculator=" + getUnitCalculator().getName() + ", ExpiryDelay=" + getExpiryDelay(); } // ----- internal helpers ----------------------------------------------- /** * {@inheritDoc} */ protected void registerKey(Object oKey, Binary binKey, Binary binValue) { registerKey(oKey, binKey, binValue, getExpiryDelay()); } /** * Register a new key for the SerializationMap. This method maintains the * internal key Set for the SerializationMap. * * @param oKey the key that has been added to the map * @param binKey the binary form of the key (null if only TTL change) * @param binValue the binary form of the value (null if only TTL change) * @param cMillis the TTL value (in milliseconds) for the entry */ protected synchronized void registerKey(Object oKey, Binary binKey, Binary binValue, int cMillis) { LongArray arrayLRU = getLruArray(); LongArray arrayExpiry = getExpiryArray(); Map mapKeys = getKeyMap(); // calculate units of storage for the new value int cOldUnits = -1; int cNewUnits = -1; if (binKey != null) { // a non-null binKey indicates the caller wants the units to be // adjusted, therefore load the value if not provided binValue = binValue == null ? getBinaryStore().load(binKey) : binValue; if (binValue != null) { UnitCalculator calculator = m_calculator; cNewUnits = calculator == null ? 1 : calculator.calculateUnits(binKey, binValue); } } // remove any previous LRU and expiry information EntryAttributes attr = (EntryAttributes) mapKeys.get(oKey); if (attr != null) { // remove old LRU touch count arrayLRU.remove(attr.getTouchCount()); // remove old expiry long ldtExpires = attr.getExpiryTime(); if (ldtExpires != 0L) { Set setKeys = (Set) arrayExpiry.get(ldtExpires); if (setKeys != null) { setKeys.remove(oKey); if (setKeys.isEmpty()) { arrayExpiry.remove(ldtExpires); } } } cOldUnits = attr.getUnits(); if (cNewUnits < 0) { cNewUnits = cOldUnits; } } // register LRU touch count long nTouch = getTouchCounter(); arrayLRU.set(nTouch, oKey); // register expiry long ldtExpires = 0L; if (cMillis > 0) { // resolution is 1/4 second (to more efficiently lump keys into // sets) ldtExpires = (getSafeTimeMillis() + cMillis) & ~0xFFL; Set setKeys = (Set) arrayExpiry.get(ldtExpires); if (setKeys == null) { setKeys = new LiteSet(); arrayExpiry.set(ldtExpires, setKeys); } setKeys.add(oKey); } // register units if (cNewUnits != cOldUnits) { m_cCurUnits += Math.max(0, cNewUnits) - Math.max(0, cOldUnits); } // register the key attr = instantiateEntryAttributes(attr, ldtExpires, nTouch, cNewUnits); mapKeys.put(oKey, attr); } /** * Touch an object to pop it to the top of the LRU * * @param oKey the key of the object to touch */ protected void touch(Object oKey) { LongArray arrayLRU = getLruArray(); Map mapKeys = getKeyMap(); synchronized (this) { if (mapKeys.containsKey(oKey)) { EntryAttributes attr = (EntryAttributes) mapKeys.get(oKey); if (attr != null) { // remove old LRU touch count arrayLRU.remove(attr.getTouchCount()); } // register LRU touch count long nTouch = getTouchCounter(); arrayLRU.set(nTouch, oKey); attr = instantiateEntryAttributes(attr, attr.getExpiryTime(), nTouch, attr.getUnits()); mapKeys.put(oKey, attr); } } } /** * Unregister a key from the SerializationMap. This method maintains the * internal key Set for the SerializationMap. * * @param oKey the key that has been removed from the map */ protected void unregisterKey(Object oKey) { LongArray arrayLRU = getLruArray(); LongArray arrayExpiry = getExpiryArray(); Map mapKeys = getKeyMap(); synchronized (this) { EntryAttributes attr = (EntryAttributes) mapKeys.remove(oKey); if (attr != null) { // remove old LRU info arrayLRU.remove(attr.getTouchCount()); // remove old expiry info long ldtPrevExpires = attr.getExpiryTime(); if (ldtPrevExpires != 0L) { Set setKeys = (Set) arrayExpiry.get(ldtPrevExpires); if (setKeys != null) { setKeys.remove(oKey); if (setKeys.isEmpty()) { arrayExpiry.remove(ldtPrevExpires); } } } // adjust units m_cCurUnits -= attr.getUnits(); } } } /** * Make sure the size of the cache isn't too big. */ protected void checkSize() { long cMax = m_cMaxUnits; if (cMax > 0 && cMax < m_cCurUnits && m_apprvrEvict != EvictionApprover.DISAPPROVER) { synchronized (this) { LongArray arrayLRU = getLruArray(); while (m_cCurUnits > getLowUnits()) { Object oKey = arrayLRU.get(arrayLRU.getFirstIndex()); if (oKey != null) { evict(oKey); } } } } } /** * Make sure that the cache does not contain expired items. */ protected void checkExpiry() { // most of the time, expiry probably isn't even used LongArray arrayExpiry = getExpiryArray(); if (arrayExpiry.isEmpty()) { return; } // check if the current time indicates that some entries have expired long ldtCurrent = getSafeTimeMillis(); if (ldtCurrent > arrayExpiry.getFirstIndex()) { synchronized (this) { Set setEvict = null; for (LongArray.Iterator iter = arrayExpiry.iterator(); iter.hasNext(); ) { Set setKeys = (Set) iter.next(); if (setKeys != null && ldtCurrent > iter.getIndex()) { iter.remove(); if (setEvict == null) { setEvict = setKeys; } else { setEvict.addAll(setKeys); } } else { break; } } if (setEvict != null) { evictAll(setEvict); } } } } // ----- inner class: EntryAttributes ----------------------------------- /** * Factory pattern: Construct an attribute holder for an entry. * * @param attrOrig the previous attributes for this entry, if any * @param ldtExpires the date/time at which the entry expires, or zero * @param nTouch the touch counter assigned to the entry * @param cUnits the number of storage units used by the entry * * @return the instantiated {@link EntryAttributes} */ public EntryAttributes instantiateEntryAttributes( EntryAttributes attrOrig, long ldtExpires, long nTouch, int cUnits) { return new EntryAttributes(ldtExpires, nTouch, cUnits); } /** * A class that holds on to the expiry time and touch order for an entry. */ protected class EntryAttributes extends Base { /** * Construct an attribute holder for an entry. * * @param ldtExpires the date/time at which the entry expires, or * zero * @param nTouch the touch counter assigned to the entry * @param cUnits the number of storage units used by the entry */ public EntryAttributes(long ldtExpires, long nTouch, int cUnits) { m_ldtExpires = ldtExpires; m_nTouch = nTouch; m_cUnits = cUnits; } /** * Determine the date/time at which the entry expires. * * @return the system time at which the corresponding entry expires, * or zero if the entry never expires */ public long getExpiryTime() { return m_ldtExpires; } /** * Determine the absolute order of the entry within in the LRU list. * * @return the touch counter assigned to the corresponding entry */ public long getTouchCount() { return m_nTouch; } /** * Determine the number of units of storage used by the entry. * * @return the storage units used by the entry */ public int getUnits() { return m_cUnits; } /** * The date/time at which the corresponding entry expires. */ private long m_ldtExpires; /** * The touch counter for the entry. This is the absolute order of the * entry in the LRU list. */ private long m_nTouch; /** * The number of units of storage used by the entry. */ private int m_cUnits; } // ----- inner class: EntrySet ------------------------------------------ /** * {@inheritDoc} */ protected Set instantiateEntrySet() { return new EntrySet(); } /** * A set of entries backed by this map. */ public class EntrySet extends AbstractKeySetBasedMap.EntrySet { // ----- inner class: Entry ------------------------------------- /** * {@inheritDoc} */ protected Map.Entry instantiateEntry(Object oKey, Object oValue) { return new Entry(oKey, oValue); } /** * A Cache Entry implementation. */ public class Entry extends AbstractKeyBasedMap.EntrySet.Entry implements ConfigurableCacheMap.Entry { /** * Construct an Entry. * * @param oKey the Entry key * @param oValue the Entry value (optional) */ public Entry(Object oKey, Object oValue) { super(oKey, oValue); } /** * {@inheritDoc} */ public void touch() { SerializationCache.this.touch(getKey()); } /** * {@inheritDoc} */ public int getTouchCount() { EntryAttributes attr = getAttributes(); return (int) Math.max(attr == null ? 0L : attr.getTouchCount(), Integer.MAX_VALUE); } /** * {@inheritDoc} */ public long getLastTouchMillis() { // value is unknown return 0L; } /** * {@inheritDoc} */ public long getExpiryMillis() { EntryAttributes attr = getAttributes(); return attr == null ? 0L : attr.getExpiryTime(); } /** * {@inheritDoc} */ public void setExpiryMillis(long lMillis) { EntryAttributes attr = getAttributes(); if (attr != null) { registerKey(getKey(), null, null, (int) Math.min(Math.max(1L, lMillis - getSafeTimeMillis()), Integer.MAX_VALUE)); } } /** * {@inheritDoc} */ public int getUnits() { EntryAttributes attr = getAttributes(); return attr == null ? -1 : attr.getUnits(); } /** * {@inheritDoc} */ public void setUnits(int cUnits) { SerializationCache cache = SerializationCache.this; synchronized (cache) { EntryAttributes attr = getAttributes(); if (attr != null) { int cOld = attr.getUnits(); if (cOld >= 0 && cUnits >= 0 && cOld != cUnits) { // adjust the units of the entry cache.getKeyMap().put(getKey(), cache.instantiateEntryAttributes(attr, attr.getExpiryTime(), getTouchCount(), cUnits)); // adjust total units cache.m_cCurUnits += (cUnits - cOld); } } } } /** * Obtain the cache attributes for this entry. * * @return the entry attributes for this entry */ protected EntryAttributes getAttributes() { return (EntryAttributes) SerializationCache.this.getKeyMap().get(getKey()); } } } // ----- inner class: InternalEvictionPolicy ---------------------------- /** * The InternalEvictionPolicy represents a pluggable eviction policy for * the non-pluggable built-in (internal) eviction policies supported by * this cache implementation. */ public static class InternalEvictionPolicy implements EvictionPolicy { /** * Default constructor. */ private InternalEvictionPolicy() { } /** * {@inheritDoc} */ public void entryTouched(ConfigurableCacheMap.Entry entry) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public void requestEviction(int cMaximum) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public String getName() { return "Internal-LRU"; } static final InternalEvictionPolicy INSTANCE = new InternalEvictionPolicy(); } // ----- data fields ---------------------------------------------------- /** * Maximum number of units to manage in the cache. */ protected long m_cMaxUnits; /** * Current number of units to in the cache. */ protected long m_cCurUnits; /** * Default expiry for entries added to the cache. This is a TTL value, * expressed in milliseconds. Zero indicates no expiry. A positive value * indicates the number of milliseconds that an entry will be valid after * it is placed into the cache, assuming a specific TTL is not provided * with the entry. */ private int m_cDefaultTTLMillis; /** * The number of units to prune the cache down to. */ protected long m_cPruneUnits; /** * Touch counter (for LRU). */ private long m_cTouches; /** * Array of keys, indexed by the touch counter. */ private LongArray m_arrayLRU; /** * Array of set of keys, indexed by the time of expiry. */ private LongArray m_arrayExpiry; /** * The unit calculator to use to limit size of the cache. */ protected UnitCalculator m_calculator; /** * The unit factor. */ protected int m_nUnitFactor = 1; /** * The EvictionApprover. */ protected EvictionApprover m_apprvrEvict; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy