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

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

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.tangosol.net.cache;


import com.oracle.coherence.common.base.Blocking;

import com.tangosol.io.nio.BinaryMap;
import com.tangosol.net.NamedCache;
import com.tangosol.util.AbstractKeyBasedMap;
import com.tangosol.util.Base;
import com.tangosol.util.Converter;
import com.tangosol.util.ConverterEnumerator;
import com.tangosol.util.Filter;
import com.tangosol.util.FilterEnumerator;
import com.tangosol.util.Gate;
import com.tangosol.util.ImmutableArrayList;
import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;
import com.tangosol.util.MultiplexingMapListener;
import com.tangosol.util.NullImplementation;
import com.tangosol.util.ObservableMap;
import com.tangosol.util.RecyclingLinkedList;
import com.tangosol.util.SafeHashMap;
import com.tangosol.util.ThreadGateLite;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;


/**
* A non-observable Map implementation that wraps two maps - a front map
* (assumed to be fast but limited in its maximum size) and a back map
* (assumed to be slower but much less limited in its maximum size).
* 

* The SimpleOverflowMap is not observable, and as such it is assumed to be * a passive data structure. In other words, it does not support item * expiration or other types of "self-generating" events. As such, it may * be more efficient for many common use cases that would benefit from the * complete avoidance of event handling. As a second effect of being a passive * data structure, the implementation is able to avoid tracking all of its * entries; instead it tracks only entries that are in the front map and those * that currently have a pending event or an in-flight operation occurring. * This means that huge key sets are possible, since only keys in the front * map will be managed in memory by the overflow map, but it means that some * operations that would benefit from knowing the entire set of keys will be * more expensive. Examples of operations that would be less efficient as a * result include {@link #containsKey containsKey()}, {@link #size()}, * {@link com.tangosol.util.AbstractKeyBasedMap.KeySet#iterator * keySet().iterator()}, etc. * * @since Coherence 3.1 * @author cp 2005.07.11 */ public class SimpleOverflowMap extends AbstractKeyBasedMap { // ----- constructors --------------------------------------------------- /** * Construct a SimpleOverflowMap using two specified maps: *

    *
  • FrontMap (aka "cache" or "shallow") and *
  • BackMap (aka "file" or "deep") *
* * @param mapBack back map * @param mapFront front map */ public SimpleOverflowMap(ObservableMap mapFront, Map mapBack) { this(mapFront, mapBack, null); } /** * Construct a SimpleOverflowMap using three specified maps: *
    *
  • Front Map (aka "cache" or "shallow") and *
  • Back Map (aka "file" or "deep") *
  • Miss Cache *
* * @param mapFront front map * @param mapBack back map * @param mapMiss an optional miss cache */ public SimpleOverflowMap(ObservableMap mapFront, Map mapBack, Map mapMiss) { azzert(mapFront != null && mapBack != null); m_mapFront = mapFront; m_mapBack = mapBack; // get the keys in the front map (i.e. handle the case in which the // front map already has something in it); unfortunately, it is // [theoretically] possible for changes to occur in between this step // and when we put the listener on, but the listener can't be added // until the status map is prepared ImmutableArrayList setInitial = new ImmutableArrayList(mapFront.keySet()); if (!setInitial.isEmpty()) { Map mapStatus = getStatusMap(); for (Iterator iter = setInitial.iterator(); iter.hasNext(); ) { Object oKey = iter.next(); Status status = new Status(); status.setEntryInFront(true); status.setBackUpToDate(false); mapStatus.put(oKey, status); } } setFrontMapListener(instantiateFrontMapListener()); // store the miss cache if (mapMiss != null && !mapMiss.isEmpty()) { mapMiss.clear(); } m_mapMiss = mapMiss; // double-check that everything is ready to go if (!setInitial.isEmpty()) { verifyNoNulls(mapFront.keySet(), "The front map contains a null key"); if (!isNullValuesAllowed()) { verifyNoNulls(mapFront.values(), "NullValuesAllowed is false" + " but the front map contains at least one null value"); } // double check that the front map didn't change in the meantime azzert(mapFront.keySet().equals(setInitial)); } // come up with a good default for the FrontPutBlind setting; for // each of these classes, it is assumed that the cost of using a temp // map with a putAll() is less than the cost of returning the orig // value from a put() call if ( mapFront instanceof NamedCache || mapFront instanceof CachingMap || mapFront instanceof SerializationMap || mapFront instanceof BinaryMap || mapFront instanceof OverflowMap || mapFront instanceof SimpleOverflowMap) { setFrontPutBlind(true); } } // ----- Map interface -------------------------------------------------- /** * Clear all key/value mappings. */ @Override public void clear() { beginMapProcess(); try { // kill the listener temporarily (we don't have any desire to // listen to everything getting cleared out of the front cache) MapListener listener = getFrontMapListener(); setFrontMapListener(null); try { // clear both the front and back, which will leave the // overflow map in an empty state getFrontMap().clear(); getBackMap().clear(); // clear the statistics getCacheStatistics().resetHitStatistics(); // clear the miss map; while this is not necessary, we are // clearing everything else so it only makes sense Map mapMiss = getMissCache(); if (mapMiss != null) { mapMiss.clear(); } // clean up the status objects Map mapStatus = getStatusMap(); synchronized (mapStatus) { for (Iterator iter = mapStatus.values().iterator(); iter.hasNext(); ) { Status status = (Status) iter.next(); synchronized (status) { // since no one else is processing, the status // should be available assert status.isOwnedByCurrentThread(); // clear the status status.setEntryInFront(false); status.setBackUpToDate(true); // clear any pending event status.takeEvent(); } } } // clear out the list of statuses that have deferred events getDeferredList().clear(); } finally { setFrontMapListener(listener); } } finally { endMapProcess(); } } /** * Returns true if this map contains a mapping for the specified * key. * * @return true if this map contains a mapping for the specified * key, false otherwise. */ @Override public boolean containsKey(Object oKey) { Status status = beginKeyProcess(oKey); try { if (status.isEntryInFront()) { return true; } Map mapMiss = getMissCache(); if (mapMiss != null && mapMiss.containsKey(oKey)) { return false; } boolean fContains = getBackMap().containsKey(oKey); if (!fContains && mapMiss != null) { mapMiss.put(oKey, null); } return fContains; } finally { endKeyProcess(oKey, status); } } /** * Returns the value to which this map maps the specified key. * * @param oKey the key object * * @return the value to which this map maps the specified key, * or null if the map contains no mapping for this key */ @Override public Object get(Object oKey) { Object oValue = null; boolean fContains = false; long ldtStart = getSafeTimeMillis(); Status status = beginKeyProcess(oKey); try { if (status.isEntryInFront()) { fContains = true; Map mapFront = getFrontMap(); oValue = mapFront.get(oKey); MapEvent evt; synchronized (mapFront) { evt = status.closeProcessing(); } if (evt != null) { switch (evt.getId()) { case ENTRY_INSERTED: case ENTRY_UPDATED: if (oValue == null) { oValue = evt.getNewValue(); } break; case ENTRY_DELETED: if (oValue == null) { oValue = evt.getOldValue(); } break; } processFrontEvent(status, evt); } } else { Map mapMiss = getMissCache(); if (mapMiss == null || !mapMiss.containsKey(oKey)) { Map mapBack = getBackMap(); oValue = mapBack.get(oKey); fContains = oValue != null || isNullValuesAllowed() && mapBack.containsKey(oKey); if (fContains) { Map mapFront = getFrontMap(); putOne(mapFront, oKey, oValue, isFrontPutBlind()); status.setEntryInFront(true); status.setBackUpToDate(true); MapEvent evt; synchronized (mapFront) { evt = status.closeProcessing(); } if (evt != null) { // we're expecting an inserted event, and so it can be // safely ignored if (evt.getId() != ENTRY_INSERTED) { processFrontEvent(status, evt); } } } else if (mapMiss != null) { mapMiss.put(oKey, null); } } } } finally { endKeyProcess(oKey, status); } // update statistics if (fContains) { m_stats.registerHit(ldtStart); } else { m_stats.registerMiss(ldtStart); } return oValue; } /** * Returns true if this map contains no key-value mappings. * * @return true if this map contains no key-value mappings */ @Override public boolean isEmpty() { // from a concurrency point of view, it is possible to conceive of a // situation in which isEmpty could return false when the map is not // actually empty, but the converse is not true if (getFrontMap().isEmpty() && getBackMap().isEmpty()) { // the expense of synchronization etc. is therefore only incurred // when the map appears to be empty, which is unlikely to occur // often, and when it does, the expense of obtaining exclusive // ownership of all the status objects is minimized (since there // are none) beginMapProcess(); try { return getFrontMap().isEmpty() && getBackMap().isEmpty(); } finally { endMapProcess(); } } else { return false; } } /** * Associates the specified value with the specified key in this map. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * * @return previous value associated with specified key, or null * if there was no mapping for key */ @Override public Object put(Object oKey, Object oValue) { if (oValue == null && !isNullValuesAllowed()) { throw new IllegalArgumentException("null value is unsupported" + "(key=\"" + oKey + "\")"); } long ldtStart = getSafeTimeMillis(); Object oOldValue = null; Status status = beginKeyProcess(oKey); try { Map mapFront = getFrontMap(); Map mapBack = getBackMap(); Map mapMiss = getMissCache(); boolean fEntryWasInFront = status.isEntryInFront(); boolean fBackWasUpToDate = status.isBackUpToDate(); boolean fOldValue = false; boolean fEvicted = false; // store the new value in the front map if (fEntryWasInFront) { oOldValue = mapFront.put(oKey, oValue); if (oOldValue != null) { fOldValue = true; } } else { putOne(mapFront, oKey, oValue, isFrontPutBlind()); } status.setEntryInFront(true); status.setBackUpToDate(false); // handle any events from the front MapEvent evt = status.closeProcessing(); if (evt == null) { // unless we can grab the old value from the back, it's // been lost (since we didn't get any event); just assume // it was null if (!fOldValue && fEntryWasInFront && !fBackWasUpToDate) { fOldValue = true; } } else { switch (evt.getId()) { case ENTRY_INSERTED: case ENTRY_UPDATED: // just what we expected if (fEntryWasInFront && !fOldValue) { oOldValue = evt.getOldValue(); fOldValue = true; } break; case ENTRY_DELETED: if (fEntryWasInFront && !fOldValue) { oOldValue = evt.getOldValue(); fOldValue = true; } status.setEntryInFront(false); fEvicted = true; break; } } // no longer a miss (we just put it into the overflow map) if (mapMiss != null && mapMiss.containsKey(oKey)) { mapMiss.remove(oKey); // old value is null and thus cannot be obtained fOldValue = true; } // back map processing (unless we already have the return value // and the entry is safely stored in the front) if (fEvicted && !fOldValue) { oOldValue = mapBack.put(oKey, oValue); } else if (fEvicted) { putOne(mapBack, oKey, oValue, true); status.setBackUpToDate(true); } else if (!fOldValue) { assert !fEntryWasInFront; oOldValue = mapBack.get(oKey); } } finally { endKeyProcess(oKey, status); } // update statistics m_stats.registerPut(ldtStart); return oOldValue; } /** * Copies all of the mappings from the specified map to this map. The * effect of this call is equivalent to that of calling {@link #put} * on this map once for each mapping in the passed map. The behavior of * this operation is unspecified if the passed map is modified while the * operation is in progress. * * @param map the Map containing the key/value pairings to put into this * Map */ @Override public void putAll(Map map) { long ldtStart = getSafeTimeMillis(); Object[] aoKey; if (isNullValuesAllowed()) { aoKey = map.keySet().toArray(); } else { // perhaps this is overkill, and it isn't even provably correct // since we can't prevent modifications from occurring to the // passed map on a different thread while we're processing here, // but some functionality counts on there being no null values if // the option for null values has not been enabled int i = 0, c = map.size(); aoKey = new Object[c]; for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); try { aoKey[i++] = entry.getKey(); } catch (ArrayIndexOutOfBoundsException e) { throw Base.ensureRuntimeException(e, "a modification was detected in the passed map during iteration"); } if (entry.getValue() == null) { throw new IllegalArgumentException("null value is unsupported" + "(putAll: " + map + ")"); } } if (i != c) { throw new IllegalStateException("a modification was" + " detected in the passed map during iteration"); } } Status[] astatus = beginBulkKeyProcess(aoKey); try { Map mapFront = getFrontMap(); mapFront.putAll(map); // process the inserts/updates for (int i = 0, c = astatus.length; i < c; ++i) { Status status = astatus[i]; // handle any events from the front MapEvent evt; synchronized (mapFront) { evt = status.closeProcessing(); } if (evt != null) { switch (evt.getId()) { case ENTRY_INSERTED: case ENTRY_UPDATED: status.setEntryInFront(true); status.setBackUpToDate(false); break; case ENTRY_DELETED: Object oKey = evt.getKey(); putOne(getBackMap(), oKey, map.get(oKey), true); status.setEntryInFront(false); status.setBackUpToDate(true); break; } } } // all the keys we just put in are no longer misses Map mapMiss = getMissCache(); if (mapMiss != null) { mapMiss.keySet().removeAll(map.keySet()); } } finally { endBulkKeyProcess(aoKey, astatus); } // update statistics m_stats.registerPuts(map.size(), ldtStart); } /** * Removes the mapping for this key from this map if present. * Expensive: updates both the underlying cache and the local cache. * * @param oKey key whose mapping is to be removed from the map * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values. */ @Override public Object remove(Object oKey) { Object oValue = null; Status status = beginKeyProcess(oKey); try { if (status.isEntryInFront()) { Map mapFront = getFrontMap(); mapFront.keySet().remove(oKey); MapEvent evt; synchronized (mapFront) { evt = status.closeProcessing(); } if (evt != null) { if (evt.getId() == ENTRY_DELETED) { oValue = evt.getOldValue(); // clean up the status so it is discardable status.setEntryInFront(false); status.setBackUpToDate(true); } else { processFrontEvent(status, evt); } } // remove the entry from the back map as well getBackMap().keySet().remove(oKey); } else { Map mapMiss = getMissCache(); if (mapMiss == null || !mapMiss.containsKey(oKey)) { Map mapBack = getBackMap(); boolean fContains = mapBack.containsKey(oKey); if (fContains) { oValue = mapBack.remove(oKey); } else if (mapMiss != null) { mapMiss.put(oKey, null); } } } } finally { endKeyProcess(oKey, status); } return oValue; } /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map */ @Override public int size() { try { Map mapBack = getBackMap(); int c = mapBack.size(); for (Iterator iter = getStatusMap().entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); Status status = (Status) entry.getValue(); if (status.isValid() && status.isEntryInFront() && !mapBack.containsKey(entry.getKey())) { ++c; } } return c; } catch (ConcurrentModificationException e) { return super.size(); } } // ----- AbstractKeyBasedMap methods ------------------------------------ /** * Create an iterator over the keys in this Map. * * @return a new instance of an Iterator over the keys in this Map */ @Override protected Iterator iterateKeys() { return new KeyIterator(); } /** * Removes the mapping for this key from this map if present. This method * exists to allow sub-classes to optmiize remove functionalitly for * situations in which the original value is not required. * * @param oKey key whose mapping is to be removed from the map * * @return true iff the Map changed as the result of this operation */ @Override protected boolean removeBlind(Object oKey) { boolean fDidContain = false; Status status = beginKeyProcess(oKey); try { if (status.isEntryInFront()) { Map mapFront = getFrontMap(); mapFront.keySet().remove(oKey); MapEvent evt; synchronized (mapFront) { evt = status.closeProcessing(); } if (evt != null) { if (evt.getId() == ENTRY_DELETED) { // clean up the status so it is discardable status.setEntryInFront(false); status.setBackUpToDate(true); } else { processFrontEvent(status, evt); } } // remove the entry from the back map as well getBackMap().keySet().remove(oKey); fDidContain = true; } else { Map mapMiss = getMissCache(); if (mapMiss == null || !mapMiss.containsKey(oKey)) { fDidContain = getBackMap().keySet().remove(oKey); if (!fDidContain && mapMiss != null) { mapMiss.put(oKey, null); } } } } finally { endKeyProcess(oKey, status); } return fDidContain; } // ----- accessors ------------------------------------------------------ /** * Returns the front Map. *

* Warning: Direct modifications of the returned map may cause * unpredictable behavior of the Overflow Map. * * @return the front Map */ public ObservableMap getFrontMap() { return m_mapFront; } /** * Returns the back Map. *

* Warning: Direct modifications of the returned map may cause * unpredictable behavior of the Overflow Map. * * @return the back Map */ public Map getBackMap() { return m_mapBack; } /** * Returns the optional miss cache. *

* Warning: Direct modifications of the returned map may cause * unpredictable behavior of the Overflow Map. * * @return the miss cache, or null if there is no miss cache */ public Map getMissCache() { return m_mapMiss; } /** * Set Miss Cache. * * Note: do not use other than in OverflowScheme#realizeMap(...) * * @param mapMiss miss cache. */ public void setMissCache(Map mapMiss) { m_mapMiss = mapMiss; } /** * Returns the CacheStatistics for this cache. * * @return a CacheStatistics object */ public CacheStatistics getCacheStatistics() { return m_stats; } /** * Obtain the Gate for managing key-level and Collection-level * operations against the Map, versus Map-level operations themselves. * * @return the Gate for the OverflowMap */ protected Gate getGate() { return m_gate; } /** * Obtain the Map of Status objects for entries managed by this * Overflow Map. There will be a Status object for each entry in * the front map, and a Status object for each key that has a current * operation underway. * * @return the Map of Status objects */ protected Map getStatusMap() { return m_mapStatus; } /** * Obtain the List of keys that may have deferred events. * * @return the List of keys of deferred-event Status objects */ protected List getDeferredList() { return m_listDeferred; } /** * Determine if null values are permitted. By default, they are not. * * @return true iff null values are permitted */ public boolean isNullValuesAllowed() { return m_fNullValuesAllowed; } /** * Specify whether null values are permitted. * * @param fAllowNulls pass true to allow null values; false to * disallow */ public synchronized void setNullValuesAllowed(boolean fAllowNulls) { beginMapProcess(); try { synchronized (this) { if (fAllowNulls != isNullValuesAllowed()) { if (!fAllowNulls && !getStatusMap().isEmpty()) { azzert(!values().contains(null), "NullValuesAllowed cannot be set to false because" + " the SimpleOverflowMap contains null values"); } m_fNullValuesAllowed = fAllowNulls; } } } finally { endMapProcess(); } } /** * Determine if the front Map is more efficiently updated using putAll. * (The use of putAll instead of put is called put-blind * because it does not return a value.) * * @return true iff the use of the put method should be avoided * when updating the front Map, and putAll should be used * instead */ public boolean isFrontPutBlind() { return m_fUseFrontPutAll; } /** * Specify whether the front Map is more efficiently updated using putAll. * * @param fUseFrontPutAll pass true to allow null values; false to * disallow */ public void setFrontPutBlind(boolean fUseFrontPutAll) { m_fUseFrontPutAll = fUseFrontPutAll; } // ----- front map listener --------------------------------------------- /** * Get the MapListener for the front map. * * @return the MapListener for the front map */ protected MapListener getFrontMapListener() { return m_listenerFront; } /** * Specify the MapListener for the front map. *

* The caller is required to manage all of the thread concurrency issues * associated with modifying the listener. * * @param listener the MapListener for the front map */ protected void setFrontMapListener(MapListener listener) { ObservableMap mapFront = getFrontMap(); azzert(mapFront != null); MapListener listenerOld = m_listenerFront; if (listener != listenerOld) { if (listenerOld != null) { mapFront.removeMapListener(listenerOld); m_listenerFront = null; } if (listener != null) { mapFront.addMapListener(listener); m_listenerFront = listener; } } } /** * Factory pattern: Front Map Listener. * * @return a new instance of the listener for the front map */ protected MapListener instantiateFrontMapListener() { return new FrontMapListener(); } /** * A listener for the front map that moves evictions to the back map. */ protected class FrontMapListener extends MultiplexingMapListener { /** * {@inheritDoc} */ @Override protected void onMapEvent(MapEvent evt) { onFrontEvent(evt); } } /** * Either handle an event by turning it over to another thread that is * processing the key specified by the event, or take responsibility on * this thread for deferring the event and registering its immediate * side-effects. * * @param evt the event */ protected void onFrontEvent(MapEvent evt) { // an event will always be a re-entrant condition unless one of the // two simple contracts are broken: // 1) daemon threads evicting data // 2) someone modifying the front map directly Gate gate = getGate(); boolean fReentrant = gate.isEnteredByCurrentThread(); if (!fReentrant) { // could issue a warning here, but Coherence internally uses // OverflowMap for a graveyard implementation and directly evicts // from the front map gate.enter(-1); } try { Map mapStatus = getStatusMap(); Object oKey = evt.getKey(); Status status; while (true) { synchronized (mapStatus) { status = (Status) mapStatus.get(oKey); if (status == null) { status = instantiateStatus(); mapStatus.put(oKey, status); } } // must exit sync for Map before entering sync for Status synchronized (status) { // verify that this Status is *the* Status for that key if (status.isValid()) { // determine if the event may be deferred, which // implies that we need to queue the status to be // processed if (status.registerFrontEvent(evt)) { getDeferredList().add(oKey); } return; } } } } finally { if (!fReentrant) { gate.exit(); } } } /** * Process an event. The Status must already be owned by this thread and * in the processing or committing state. * * @param status the status * @param evt the event to process; may be null */ protected void processFrontEvent(Status status, MapEvent evt) { assert status.isOwnedByCurrentThread() && (status.isProcessing() || status.isCommitting()); if (evt != null) { // it is necessary to synchronize on the status at this point to // prevent the registration of further events against the same entry synchronized (status) { switch (evt.getId()) { case ENTRY_INSERTED: status.setEntryInFront(true); status.setBackUpToDate(false); // remove from miss cache; this is only necessary if // front map is being manipulated directly Map mapMiss = getMissCache(); if (mapMiss != null) { mapMiss.remove(evt.getKey()); } break; case ENTRY_UPDATED: status.setEntryInFront(true); status.setBackUpToDate(false); break; case ENTRY_DELETED: status.setEntryInFront(false); if (!status.isBackUpToDate()) { getBackMap().put(evt.getKey(), evt.getOldValue()); status.setBackUpToDate(true); } break; } } } } // ----- process registration and management ---------------------------- /** * Block until a key is available for processing, and return the Status * object for that key. The caller is then expected to perform its * processing, during which time events against the Map Entry may occur. * Note that those events can occur on threads other than the current * thread; when this current thread can no longer handle events from other * threads, it must call {@link Status#closeProcessing()} and then perform * its final adjustments to the Status data structure, handling any events * that were queued in the meantime on the Status. After completing the * processing during this "quiet period" in which all other threads are * prevented from accessing this entry or handling events for this entry, * then the caller must call {@link #endKeyProcess} passing the Status * object returned from this method. * * @param oKey the key to process * * @return the Status object for the specified key */ protected Status beginKeyProcess(Object oKey) { // check for re-entrancy Gate gate = getGate(); boolean fReentrant = gate.isEnteredByCurrentThread(); gate.enter(-1); // handle up to one deferred event for any key (other than the key // we've been asked to process) if (!fReentrant) { // check for and process deferred events from the front Map processDeferredEvents(); } // obtain the key that we've been asked to process while (true) { Status status; Map mapStatus = getStatusMap(); synchronized (mapStatus) { status = (Status) mapStatus.get(oKey); if (status == null) { status = instantiateStatus(); mapStatus.put(oKey, status); } } // must exit sync for Map before entering sync for Status synchronized (status) { // verify that this Status is *the* Status for that key if (status.isValid()) { MapEvent evtDeferred = status.waitForAvailable(); if (evtDeferred != null) { processFrontEvent(status, evtDeferred); } return status; } } } } /** * Finish the processing of a single key. * * @param oKey the key * @param status the Status object returned from the call to * {@link #beginKeyProcess} */ protected void endKeyProcess(Object oKey, Status status) { Map mapStatus = getStatusMap(); synchronized (mapStatus) { synchronized (status) { if (status.isProcessing()) { processFrontEvent(status, status.closeProcessing()); } if (status.commitAndMaybeInvalidate()) { mapStatus.remove(oKey); } } } getGate().exit(); } /** * Begin key-level procecessing for any number of keys. * * @param aoKey an array of zero or more keys; note that this array may * be re-ordered by this method * * @return an array of Status objects corresponding to the passed keys * * @see #beginKeyProcess(Object) */ protected Status[] beginBulkKeyProcess(Object[] aoKey) { int cKeys = aoKey.length; Status[] aStatus = new Status[cKeys]; switch (cKeys) { case 0: break; case 1: aStatus[0] = beginKeyProcess(aoKey[0]); break; default: { getGate().enter(-1); // check for and process deferred events from the front Map processDeferredEvents(); // obtain the ownership of the entire set of keys passed in Map mapStatus = getStatusMap(); boolean fDone = false; int cIters = 0; while (!fDone) { boolean fSuccess = true; int cReserved = 0; while (fSuccess && cReserved < cKeys) { // reserve one Object oKey = aoKey[cReserved]; Status status; synchronized (mapStatus) { status = (Status) mapStatus.get(oKey); if (status == null) { status = instantiateStatus(); mapStatus.put(oKey, status); } } // must exit sync for Map before entering sync for Status synchronized (status) { // verify that this Status is *the* Status for that key if (status.isValid()) { if (status.requestReservation()) { aStatus[cReserved++] = status; } else { fSuccess = false; } } else { fSuccess = false; } } } if (fSuccess) { // use the reservations for (int i = 0; i < cKeys; ++i) { MapEvent evtDeferred = aStatus[i].useReservation(); if (evtDeferred != null) { processFrontEvent(aStatus[i], evtDeferred); } } fDone = true; } else { // cancel reservations synchronized (mapStatus) { for (int i = 0; i < cReserved; ++i) { Status status = aStatus[i]; synchronized (status) { if (status.commitAndMaybeInvalidate()) { mapStatus.remove(aoKey[i]); } } aStatus[i] = null; } } switch (cIters++) { case 0: { // re-order the keys to minimize deadlock boolean fComparable = true; for (int i = 0; i < cKeys; ++i) { if (!(aoKey[i] instanceof Comparable)) { fComparable = false; } } if (fComparable) { Arrays.sort(aoKey); } else { Arrays.sort(aoKey, HashcodeComparator.INSTANCE); } } break; case 1: Thread.yield(); break; default: try { Blocking.sleep(cIters); } catch (InterruptedException e) { Thread.interrupted(); // at this point, this overflow map // hasn't done anything except enter the // gate, so it is possible to abort out // of this processing getGate().exit(); throw ensureRuntimeException(e); } break; } } } } break; } return aStatus; } /** * Finish the processing of any number of keys. * * @param aoKey the same array of keys as were passed to * {@link #beginBulkKeyProcess} * @param aStatus the array of Status objects returned from the call to * {@link #beginBulkKeyProcess} */ protected void endBulkKeyProcess(Object[] aoKey, Status[] aStatus) { int cStatus = aStatus.length; switch (cStatus) { case 0: break; case 1: endKeyProcess(aoKey[0], aStatus[0]); break; default: { Map mapStatus = getStatusMap(); synchronized (mapStatus) { for (int i = 0; i < cStatus; ++i) { Status status = aStatus[i]; synchronized (status) { if (status.isProcessing()) { processFrontEvent(status, status.closeProcessing()); } if (status.commitAndMaybeInvalidate()) { mapStatus.remove(aoKey[i]); } } } } getGate().exit(); } break; } } /** * Block until this thread has exclusive access to perform operations * against the OverflowMap. */ protected void beginMapProcess() { Gate gate = getGate(); gate.close(-1); gate.enter(-1); // by entering, we can later determine re-entrancy // take ownership of all of the Status objects Map mapStatus = getStatusMap(); synchronized (mapStatus) { for (Iterator iter = mapStatus.values().iterator(); iter.hasNext(); ) { Status status = (Status) iter.next(); synchronized (status) { // since no one else is processing, the status should be // available assert status.isAvailable() || status.isOwnedByCurrentThread(); // so this should not block MapEvent evtDeferred = status.waitForAvailable(); if (evtDeferred != null) { processFrontEvent(status, evtDeferred); } } } } } /** * Release exclusive access for the OverflowMap. */ protected void endMapProcess() { // commit all of the Status objects Map mapStatus = getStatusMap(); synchronized (mapStatus) { for (Iterator iter = mapStatus.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); Status status = (Status) entry.getValue(); synchronized (status) { if (status.isProcessing()) { processFrontEvent(status, status.closeProcessing()); } if (status.commitAndMaybeInvalidate()) { mapStatus.remove(entry.getKey()); } } } } Gate gate = getGate(); gate.exit(); gate.open(); } /** * Process deferred events, if there are any. This implementation * processes only the first deferred event that it encounters. */ protected void processDeferredEvents() { // process a deferred event, if there is one List listDeferred = getDeferredList(); if (!listDeferred.isEmpty()) { // try to process at least ~1% of the pending events, but not // less than 10 events and not more than 100 events int cTarget = Math.max(10, Math.min(100, listDeferred.size() >>> 7)); int cProcessed = 0; Map mapStatus = getStatusMap(); do { Object oKey = null; try { oKey = listDeferred.remove(0); } catch (Exception e) {} if (oKey != null) { Status status = (Status) mapStatus.get(oKey); if (status != null) { boolean fOwned = false; synchronized (status) { // verify that this Status is *the* Status for that key if (status.isValid()) { if (status.isAvailable()) { MapEvent evtDeferred = status.waitForAvailable(); fOwned = true; if (evtDeferred != null) { processFrontEvent(status, evtDeferred); } } } } if (fOwned) { MapEvent evtJustHappened = status.closeProcessing(); if (evtJustHappened != null) { processFrontEvent(status, evtJustHappened); } synchronized (mapStatus) { synchronized (status) { if (status.commitAndMaybeInvalidate()) { mapStatus.remove(oKey); } } } ++cProcessed; } else { // not available; defer it listDeferred.add(oKey); } } } } while (!listDeferred.isEmpty() && cProcessed < cTarget); } } // ----- internal helpers ----------------------------------------------- /** * Check the passed collection for nulls, and fail if it contains any. * * @param collection a Collection * @param sAssert the human-readable description of the error if any * nulls are found in the passed Collection */ protected static void verifyNoNulls(Collection collection, String sAssert) { boolean fHasNull = false; try { fHasNull = collection.contains(null); } catch (RuntimeException e) {} azzert(!fHasNull, sAssert); } /** * Helper to put a value into a map using either the put or * putAll method. * * @param map the Map to put into * @param oKey the key to put * @param oValue the value to put * @param fPutBlind true to use the putBlind optimization */ protected static void putOne(Map map, Object oKey, Object oValue, boolean fPutBlind) { try { if (fPutBlind) { map.putAll(Collections.singletonMap(oKey, oValue)); } else { map.put(oKey, oValue); } } catch (NullPointerException e) { out("null pointer exception occurred during putOne()" + "\nMap=" + map + "\nKey=" + oKey + "\nValue=" + oValue + "\nPutBlind=" + fPutBlind + "\nException=" + e); throw e; } } /** * Issue a one-time warning that events are being received in an order * than cannot be explained by normal operation according to the * contracts expected by this OverflowMap. * * @param evtOld the previous (potentially amalgamated) event * @param evtNew the new event */ protected static void warnEventSequence(MapEvent evtOld, MapEvent evtNew) { if (!s_fWarnedEventSequence) { s_fWarnedEventSequence = true; log("Overflow Map has detected" + " an illegal event sequence:" + "\nEvent 1: " + evtOld + "\nEvent 2: " + evtNew + "\nThis warning will not be repeated." + " Stack trace follows:\n" + getStackTrace()); } } // ----- inner class: Status -------------------------------------------- /** * Factory method: Instantiate a Status object. * * @return a new Status object */ protected Status instantiateStatus() { return new Status(); } /** * The Status object is used to manage concurrency at the key level for * the key-level operations against the Map, to track all the items in * the front Map, to manage the state transition for operations occurring * against the Map, and to coordinate events across multiple threads. */ protected static class Status { // ----- constructors ------------------------------------------- /** * Construct a Status object for a specific key. */ public Status() { } // ----- accessors ---------------------------------------------- /** * Determine the enumerated status of the Status object. This value * is intended for internal and debugging use only, and should have * no meaning to any external consumer. * * @return a number corresponding to the enumeration of states as * represented by the STATUS_* */ protected int getStatus() { return extractState(STATE_MASK_STATUS); } /** * Determine the enumerated state of the Status object. This value * is intended for internal and debugging use only, and should have * no meaning to any external consumer. * * @param nStatus a number corresponding to one of the enumeration * represented by the STATUS_* constants */ protected void setStatus(int nStatus) { updateState(STATE_MASK_STATUS, nStatus); } /** * Determine if the Status object is valid. A Status object can be * discarded (no longer used), in which case it will not be valid. * * @return true iff the Status object is still valid */ public boolean isValid() { return getStatus() != STATUS_INVALIDATED; } /** * Determine if the Status object is available. A Status object is * available if it is valid and no thread is currently processing the * entry for which this Status object exists. * * @return true iff the Status object is available */ public boolean isAvailable() { return getStatus() == STATUS_AVAILABLE; } /** * Determine if the Status object is reserved. * * @return true iff the Status object is available */ public boolean isReserved() { return getStatus() == STATUS_RESERVED; } /** * Determine if the Status object represents an Entry that is * currently being processed. * * @return true iff the entry represented by this Status object is * being processed */ public boolean isProcessing() { return getStatus() == STATUS_PROCESSING; } /** * Determine if the Status object represents an Entry that has been * processed and the results of that processing are being committed. * The "committing" status implies that no other thread is allowed to * do anything related to the entry that this Status * represents. * * @return true iff the entry represented by this Status object is * being committed */ public boolean isCommitting() { return getStatus() == STATUS_COMMITTING; } /** * Determine the thread that owns the Status object, if the Status * object is processing or committing. * * @return the owning thread, or null */ public Thread getOwnerThread() { return m_threadOwner; } /** * Specify the thread that owns the Status object. For internal use * only. * * @param thread the owning thread, or null */ protected void setOwnerThread(Thread thread) { m_threadOwner = thread; } /** * Determine if the current thread owns this Status object. * * @return true iff the current thread owns this Status object */ public boolean isOwnedByCurrentThread() { Thread thread = getOwnerThread(); return thread != null && thread == Thread.currentThread(); } /** * Determine if the entry for which this Status exists is present in * the front map. * * @return true iff the entry is stored in the front map */ public boolean isEntryInFront() { return extractFlag(STATE_MASK_FRONT); } /** * Specify whether the entry for which this Status exists is present * in the front map. * * @param fEntryInFront pass true if the entry is stored in the front * map, false if not */ public void setEntryInFront(boolean fEntryInFront) { updateFlag(STATE_MASK_FRONT, fEntryInFront); } /** * Determine if the entry for which this Status exists has the same * value in the front Map as in the back Map. * * @return true iff the value exists in the back Map, and the value in * the front Map is the same as the value in the back Map */ public boolean isBackUpToDate() { return extractFlag(STATE_MASK_INSYNC); } /** * Specify that the value stored in the back Map is known to be up to * date (not needing to be written to the back if evicted from the * front). * * @param fUpToDate whether the stored value is up to date */ public void setBackUpToDate(boolean fUpToDate) { updateFlag(STATE_MASK_INSYNC, fUpToDate); } /** * For internal use only, return the current event from the front * Map. All handling of synchronization etc. is the responsibility of * the sub-class. * * @return the cummulative front Map event for the Entry represented * by this Status object, or null if there were no events */ protected MapEvent getFrontEvent() { return m_evtFront; } /** * For internal use only, store the current event from the front Map. * All handling of synchronization etc. is the responsibility of the * sub-class. * * @param evt the cummulative front Map event for the Entry * represented by this Status object, or null to clear * the event */ protected void setFrontEvent(MapEvent evt) { m_evtFront = evt; } /** * Determine if an event has occurred against the Entry for which this * Status exists. * * @return true iff an event is held by the Status */ public boolean hasEvent() { return getFrontEvent() != null; } /** * Obtain the most recent front Map event that has occurred against * the Entry for which this Status exists. * * @return the cummulative front Map event for the Entry represented * by this Status object, or null if there were no events */ public synchronized MapEvent takeEvent() { assert isProcessing() || isCommitting(); MapEvent evt = getFrontEvent(); if (evt != null) { setFrontEvent(null); } return evt; } /** * Determine if this Status object can be discarded. *

* This is an internal method. * * @return true iff this Status object can be discarded */ protected boolean isDiscardable() { // this is the same as isAvailable() && !isEntryInFront() // && isBackUpToDate() && !hasEvent(); return extractState(STATE_MASK_RETAIN) == STATE_VALUE_RETAIN && !hasEvent(); } /** * Assemble a human-readable description. * * @return a description of this Status object */ public String getDescription() { return "Valid=" + isValid() + ", Available=" + isAvailable() + ", Processing=" + isProcessing() + ", Committing=" + isCommitting() + ", OwnerThread=" + getOwnerThread() + ", OwnedByCurrentThread=" + isOwnedByCurrentThread() + ", EntryInFront=" + isEntryInFront() + ", BackUpToDate=" + isBackUpToDate() + ", hasEvent=" + hasEvent() + ", FrontEvent=" + getFrontEvent() + ", Discardable=" + isDiscardable(); } // ----- Object methods ----------------------------------------- /** * Returns a string representation of the object. * * @return a string representation of the object */ @Override public String toString() { return "Status{" + getDescription() + '}'; } // ----- status management -------------------------------------- /** * Wait for the Entry that this Status represents to become available. * Once it becomes available, the current thread will automatically * become the owner and the status will be changed to "processing". *

* This is an internal method. It requires the caller to have * synchronized on the Status object before calling this method. * * @return whatever event was deferred for this Status which the * caller must handle */ protected MapEvent waitForAvailable() { boolean fRegisteredWaiting = false; try { while (!isAvailable()) { assert isValid(); // check for re-entrancy if (isOwnedByCurrentThread()) { throw new IllegalStateException("Re-entrancy is not supported" + " (State=" + getState() + ")"); } if (!fRegisteredWaiting) { int cWaiting = m_cWaiting & 0x000000FF; if (cWaiting == 0x000000FF) { throw new IllegalStateException( "Exceeded maximum number of waiting threads" + " (Status=" + getStatus() + ")"); } m_cWaiting = (byte) (cWaiting + 1); fRegisteredWaiting = true; } Blocking.wait(this); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw Base.ensureRuntimeException(e); } finally { if (fRegisteredWaiting) { --m_cWaiting; } } setStatus(STATUS_PROCESSING); setOwnerThread(Thread.currentThread()); return takeEvent(); } /** * Attempt to reserve the Status by marking it as committing if it is * available. If successful, the caller must subsequently either call * {@link #useReservation()} or {@link #commitAndMaybeInvalidate()} * (to cancel the reservation). *

* This is an internal method. * * @return true if the reservation was made successfull, false if * another thread already owns this Status object */ protected synchronized boolean requestReservation() { boolean fSuccess = false; if (isAvailable()) { setStatus(STATUS_RESERVED); setOwnerThread(Thread.currentThread()); fSuccess = true; } else if (isOwnedByCurrentThread()) { // re-entrancy is not permitted throw new IllegalStateException("Re-entrancy is not supported" + " (State=" + getState() + ")"); } return fSuccess; } /** * Wait for the Entry that this Status represents to no longer be * reserved. *

* This is an internal method. It requires the caller to have * synchronized on the Status object before calling this method. */ protected void waitForReservationDecision() { boolean fRegisteredWaiting = false; try { while (isReserved()) { assert isValid(); if (!fRegisteredWaiting) { int cWaiting = m_cWaiting & 0x000000FF; if (cWaiting == 0x000000FF) { throw new IllegalStateException( "Exceeded maximum number of waiting threads" + " (Status=" + getStatus() + ")"); } m_cWaiting = (byte) (cWaiting + 1); fRegisteredWaiting = true; } Blocking.wait(this); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw Base.ensureRuntimeException(e); } finally { if (fRegisteredWaiting) { --m_cWaiting; } } } /** * After having successfully made a reservation, this method completes * the reservation process by setting the status to processing for the * thread that made the reservation. *

* This is an internal method. * * @return whatever event was deferred for this Status which the * caller must handle */ protected synchronized MapEvent useReservation() { assert isReserved(); assert getOwnerThread() == Thread.currentThread(); setStatus(STATUS_PROCESSING); if (m_cWaiting != 0) { // must wake up any thread that was waiting on the // reservation status to change notifyAll(); } return takeEvent(); } /** * Finish the processing of the entry for which this Status exists and * proceed to the commit phase. The act of closing processing collects * any side-effects to the corresponding front Map Entry (either from * this or other threads) as a single event. * * @return all events that have occurred on this or other threads for * the front Map Entry represented by this Status object while * this Status object was "processing", or null if there were * no events */ public synchronized MapEvent closeProcessing() { assert isProcessing(); assert getOwnerThread() == Thread.currentThread(); setStatus(STATUS_COMMITTING); return takeEvent(); } /** * Finish the commit for the entry for which this Status exists. If * there are any threads waiting on the entry for which this Status * exists, one will be notified that the Status is now available. * If the entry for which this Status object exists is contained in * the front Map, then it will proceed to the available phase. * Otherwise, the Status will be invalidated and discarded from the * Status registry. *

* This is an internal method. It requires the caller to have first * synchronized on the registry (Map) that contains the Status objects * and then to have synchronized on this Status object itself, in that * explicit order. Failure to follow this rule will result in deadlock * and/or exceptional conditions. * * @return true iff this Status object has invalidated itself */ protected boolean commitAndMaybeInvalidate() { assert isReserved() || isCommitting(); assert isOwnedByCurrentThread(); boolean fInvalidated = false; boolean fWasReserved = isReserved(); setStatus(STATUS_AVAILABLE); if (m_cWaiting != 0) { if (fWasReserved) { // notify any thread that is waiting on the reservation // status to change notifyAll(); } else { // notify the next thread in line notify(); } } else if (isDiscardable()) { setStatus(STATUS_INVALIDATED); fInvalidated = true; } setOwnerThread(null); return fInvalidated; } // ----- event management --------------------------------------- /** * Register a MapEvent that has been raised by the front Map against * the same key for which this Status object exists. If an event has * previously been registered, the previous and new event are merged * into a single merged event that incorporates the data from both * events. *

* For truly predictable behavior, this requires that the front Map * implementation be synchronized during the raising of events, such * that an event will only be raised from the front Map on one thread * at a time. The event listener (for the overflow map) must then * synchronize on this Status object (to verify that it is indeed * valid before registering the event) and the register the event * while holding that synchronization. If the registration returns * a deferred indicator, then the * * @param evt the event that has occurred against an entry in the * front Map with the same key which this Status object * represents * * @return true iff the event processing has been deferred, implying * that this Status object should be registered in a list of * Status objects that have events that need to be handled */ public synchronized boolean registerFrontEvent(MapEvent evt) { assert isValid(); MapEvent evtOld = getFrontEvent(); if (evtOld != null) { evt = mergeEvents(evtOld, evt); } setFrontEvent(evt); // not deferred if the Status is owned and in the processing // stage (since the event will be handled by the owning thread at // the end of the processing stage) return !isProcessing(); } /** * Merge two events that have been raised in sequence from a given * map. * * @param evtOld the first event * @param evtNew the second event * * @return the merged event */ protected MapEvent mergeEvents(MapEvent evtOld, MapEvent evtNew) { MapEvent evtResult = evtNew; boolean fCreate = true; int nId = evtNew.getId(); switch ((evtOld.getId() << 2) | nId) { case (MapEvent.ENTRY_INSERTED << 2) | MapEvent.ENTRY_UPDATED: nId = ENTRY_INSERTED; break; case (MapEvent.ENTRY_INSERTED << 2) | MapEvent.ENTRY_DELETED: // while these technically cancel each other out, it // could lead to a loss of data if an evict of a key // from another thread occurs during a put of that // object that inserted that key, for example fCreate = false; if (!(evtNew instanceof CacheEvent && ((CacheEvent) evtNew).isSynthetic())) { evtResult = null; } break; case (MapEvent.ENTRY_UPDATED << 2) | MapEvent.ENTRY_UPDATED: break; case (MapEvent.ENTRY_UPDATED << 2) | MapEvent.ENTRY_DELETED: break; case (MapEvent.ENTRY_DELETED << 2) | MapEvent.ENTRY_INSERTED: // delete and insert cancel each other out and just // become an update nId = ENTRY_UPDATED; break; case (MapEvent.ENTRY_INSERTED << 2) | MapEvent.ENTRY_INSERTED: case (MapEvent.ENTRY_UPDATED << 2) | MapEvent.ENTRY_INSERTED: case (MapEvent.ENTRY_DELETED << 2) | MapEvent.ENTRY_UPDATED: case (MapEvent.ENTRY_DELETED << 2) | MapEvent.ENTRY_DELETED: default: // this is very strange ... and indicates a sequencing // problem caused by a lack of synchronization within the // map from which the event originated warnEventSequence(evtOld, evtNew); // since the result is already non-deterministic, // just use the newer event (there is no correct // answer) fCreate = false; break; } if (fCreate) { ObservableMap map = evtNew.getMap(); Object oKey = evtNew.getKey(); Object oValueOld = evtOld.getOldValue(); Object oValueNew = evtNew.getNewValue(); boolean fSynthetic = evtNew instanceof CacheEvent && ((CacheEvent) evtNew).isSynthetic(); boolean fPriming = evtNew instanceof CacheEvent && ((CacheEvent) evtNew).isPriming(); evtResult = new CacheEvent(map, nId, oKey, oValueOld, oValueNew, fSynthetic, fPriming); } return evtResult; } // ----- internal accessors ------------------------------------ /** * Determine the state of the Status object. This value is intended * for internal and debugging use only, and should have no meaning to * any external consumer. * * @return the bit-packed state of the Status object */ protected int getState() { return m_nState; } /** * Specify the state of the Status object. This value is intended * for internal and debugging use only, and should have no meaning to * any external consumer. * * @param nState the new bit-packed state for the Status object */ protected void setState(int nState) { m_nState = (byte) nState; } /** * Extract a particular masked value from the state of the Status * object. * * @param nMask the mask identifying the value * * @return the extracted value */ protected int extractState(int nMask) { return getState() & nMask; } /** * Update a particular masked value within the state of the Status * object. * * @param nMask the mask of bits to store the value within * @param nValue the value to store inside that mask */ protected void updateState(int nMask, int nValue) { assert (nValue & ~nMask) == 0; int nStateOld = getState(); int nStateNew = (nStateOld & ~nMask) | nValue; if (nStateNew != nStateOld) { setState((byte) nStateNew); } } /** * Extract a particular masked flag from the state of the Status * object. * * @param nMask the mask identifying the flag * * @return the extracted flag as a boolean */ protected boolean extractFlag(int nMask) { return extractState(nMask) != 0; } /** * Update a particular masked flag within the state of the Status * object. * * @param nMask the mask of flag bit to store the flag within * @param f the boolean value to store within that mask */ protected void updateFlag(int nMask, boolean f) { updateState(nMask, f ? nMask : 0); } // ----- constants ---------------------------------------------- /** * Bitmask for status (least significant three bits reserved). */ protected static final int STATE_MASK_STATUS = 0x07; /** * Bitmask for entry in front. */ protected static final int STATE_MASK_FRONT = 0x08; /** * Bitmask for value in front and back being in sync. */ protected static final int STATE_MASK_INSYNC = 0x10; /** * Status: The Status object exists and no thread is currently * performing processing against the associated entry. */ protected static final int STATUS_AVAILABLE = 0x00; /** * Status: The Status object has been reserved for processing by a * thread but is not yet processing. */ protected static final int STATUS_RESERVED = 0x01; /** * Status: The Status object represents an Entry that is currently * being processed. */ protected static final int STATUS_PROCESSING = 0x02; /** * Status: The Status object represents an Entry that was very * recently being processed, and is currently finalizing the results * of that processing. */ protected static final int STATUS_COMMITTING = 0x03; /** * Status: The Status object has been discarded. */ protected static final int STATUS_INVALIDATED = 0x04; /** * Bitmask for fields that would indicate that the Status must not be * discarded. */ protected static final int STATE_MASK_RETAIN = STATE_MASK_STATUS | STATE_MASK_FRONT | STATE_MASK_INSYNC; /** * Bit values for fields that would indicate that the Status can be * discarded. */ protected static final int STATE_VALUE_RETAIN = STATUS_AVAILABLE | STATE_MASK_INSYNC; // ----- data members ------------------------------------------- /** * The Thread that currently owns the Status object. */ private Thread m_threadOwner; /** * The number of other threads waiting on this Status to become * available. */ private byte m_cWaiting; /** * Current state, including status and various flags. */ private volatile byte m_nState = STATUS_AVAILABLE; /** * The event (if any) that has been received for the front Map entry * for which this Status exists. If multiple events are received, * they are merged; for truly predictable behavior, this requires that * the front Map implementation be synchronized on the portions that * are raising events, such that an event will only be raised from the * front Map on one thread at a time. The event listener must then * synchronize on this Status object (to verify that it is indeed * valid before registering the event) and the register the event * while holding that synchronization. */ private volatile MapEvent m_evtFront; } // ----- inner class: KeyIterator --------------------------------------- /** * An Iterator implementation that attempts to provide the most resilient * and most up-to-date view of the keys in the OverflowMap. This means * that it will avoid throwing a ConcurrentModificationException, and that * it will attempt to directly use the underlying iterators available for * the front and back maps. */ protected class KeyIterator implements Iterator { // ----- constructors ------------------------------------------- /** * Default constructor. */ public KeyIterator() { } // ----- Iterator interface ------------------------------------- /** * Returns true if the iteration has more elements. (In other * words, returns true if next would return an * element rather than throwing an exception.) * * @return true if the iterator has more elements */ public boolean hasNext() { if (m_fNextKeyReady) { return true; } return advance(); } /** * Returns the next element in the iteration. * * @return the next element in the iteration * * @throws java.util.NoSuchElementException if the Iterator has no more * elements */ public Object next() { if (!m_fNextKeyReady && !advance()) { throw new NoSuchElementException(); } Object oKey = m_oNextKey; m_fNextKeyReady = false; m_fCanDelete = true; m_oPrevKey = oKey; return oKey; } /** * Removes from the underlying collection the last element returned by * the iterator. * * @throws IllegalStateException if the next method has not * yet been called, or the remove method has already * been called after the last call to the next method */ public void remove() { if (m_fCanDelete) { m_fCanDelete = false; // cannot call the underlying iterator to remove the key // because the remove is logically against the overflow map // itself, not the front or back map SimpleOverflowMap.this.remove(m_oPrevKey); } else { throw new IllegalStateException(); } } // ----- internal ----------------------------------------------- /** * Advance to the next key. * * @return true if there is a next key */ protected boolean advance() { assert !m_fNextKeyReady; while (true) { Iterator iter = m_iter; boolean fNext; Object oKey; try { fNext = iter.hasNext(); oKey = fNext ? iter.next() : null; } catch (ConcurrentModificationException e) { int nMode = m_nMode; if (nMode == ITERATE_FRONT || nMode == ITERATE_BACK) { // switch to an Iterator that doesn't throw a // ConcurrentModificationException, and try again useSnapshotIterator(); continue; } else { throw e; } } if (fNext) { // if there is a previous-keys collection, then add the // key to it and at the same time verify that the key was // not already iterated Collection collPrevKeys = m_collPrevKeys; if (collPrevKeys == null || collPrevKeys.add(oKey)) { m_oNextKey = oKey; m_fNextKeyReady = true; return true; } } else { switch (m_nMode) { case ITERATE_INITIAL: useFrontIterator(); break; case ITERATE_FRONT: useBackIterator(); break; case ITERATE_BACK: case ITERATE_SNAPSHOT: // nothing else to iterate; clean up useDoneIterator(); // fall through case ITERATE_DONE: return false; } } } } /** * Switch to a snapshot iterator. */ protected void useFrontIterator() { assert m_nMode == ITERATE_INITIAL; // iterate through all the status objects Iterator iter = SimpleOverflowMap.this.getStatusMap().entrySet().iterator(); // but filter out all the ones that aren't in the front map Filter filter = FrontFilterConverter.INSTANCE; // and convert the remaining status objects into their keys Converter conv = FrontFilterConverter.INSTANCE; m_nMode = ITERATE_FRONT; m_iter = new ConverterEnumerator( (Iterator) new FilterEnumerator(iter, filter), conv); m_collPrevKeys = new ArrayList(); } /** * Switch to an iterator over the back map. */ protected void useBackIterator() { assert m_nMode == ITERATE_FRONT; // iterate through all the keys in the back map Iterator iter = SimpleOverflowMap.this.getBackMap().keySet().iterator(); m_nMode = ITERATE_BACK; m_iter = iter; // we need to switch from a List (used on the front) to a Set // because we need to both add what we're iterating *and* check // for uniqueness m_collPrevKeys = new HashSet(m_collPrevKeys); } /** * Switch to a snapshot iterator. */ protected void useSnapshotIterator() { Map mapStatus = SimpleOverflowMap.this.getStatusMap(); Map mapBack = SimpleOverflowMap.this.getBackMap(); // we're going to build a list of all keys HashSet setKeys = new HashSet(mapStatus.size() + mapBack.size()); // add front keys Map.Entry[] aEntry = (Map.Entry[]) mapStatus.entrySet().toArray(ENTRY_ARRAY); for (int i = 0, c = aEntry.length; i < c; ++i) { Map.Entry entry = aEntry[i]; Status status = (Status) entry.getValue(); if (status.isValid() && status.isEntryInFront()) { setKeys.add(entry.getKey()); } } aEntry = null; // clear it out; it could be a big array // add back keys try { setKeys.addAll(mapBack.keySet()); } catch (ConcurrentModificationException e) { // synchronizing totally sucks, but this should never happen // in the first place with any of the built-in Coherence map // implementations, and the whole point of this is to have // as close to no chance of ConcurrentModificationException // as is possible Object[] aoKey; synchronized (mapBack) { aoKey = mapBack.keySet().toArray(); } setKeys.addAll(new ImmutableArrayList(aoKey)); aoKey = null; // clear it out; it could be a big array } // remove any previously iterated keys Collection collPrevKeys = m_collPrevKeys; if (collPrevKeys != null) { setKeys.removeAll(collPrevKeys); } m_nMode = ITERATE_SNAPSHOT; m_iter = setKeys.iterator(); m_collPrevKeys = null; } /** * Switch to an iterator over nothing. */ protected void useDoneIterator() { m_nMode = ITERATE_DONE; m_iter = NullImplementation.getIterator(); m_collPrevKeys = null; m_oNextKey = null; } // ----- constants ---------------------------------------------- /** * Unitialized iteration mode. */ private static final int ITERATE_INITIAL = 0; /** * Iteration mode that iterates only keys in the FrontMap. For this * mode, m_iter is an Iterator of Status objects in the * StatusMap. */ private static final int ITERATE_FRONT = 1; /** * Iteration mode that iterates only keys in the BackMap. For this * mode, m_iter is an Iterator of keys in the BackMap. */ private static final int ITERATE_BACK = 2; /** * Iteration mode that iterates over a snap-shot of all keys in the * OverflowMap. */ private static final int ITERATE_SNAPSHOT = 3; /** * Nothing is left to iterate. */ private static final int ITERATE_DONE = 4; // ----- data members ------------------------------------------- /** * The current iteration mode; one of the ITERATE_* * constants. */ private int m_nMode = ITERATE_INITIAL; /** * The current underlying iterator of keys. */ private Iterator m_iter = NullImplementation.getIterator(); /** * The Collection of keys already iterated. If this is null, then * the keys being iterated are not collected. */ private Collection m_collPrevKeys; /** * Set to true when m_oNextKey is the next key to return * from the iterator. If there is no next key, or if the next key is * not determined yet, then this will be false. Set up by * {@link #advance} and reset by {@link #next}. */ private boolean m_fNextKeyReady; /** * The next key to return from this iterator. Set up by * {@link #advance} and reset by {@link #next}. */ private Object m_oNextKey; /** * Set to true when the m_oPrevKey key has been returned but * not yet removed. Set up by {@link #next} and reset by * {@link #remove()}. */ private boolean m_fCanDelete; /** * The key that can be deleted, if any. Set up by {@link #next}. */ private Object m_oPrevKey; } // ----- inner class: FrontFilterConverter ------------------------------ /** * A combination Filter and Converter used to iterate through the * status map in order to iterate through the front keys. */ protected static class FrontFilterConverter implements Filter, Converter { /** * Filters keys out that are not in the front map. */ public boolean evaluate(Object o) { Status status = (Status) ((Map.Entry) o).getValue(); return status.isValid() && status.isEntryInFront(); } /** * Extracts the key from the Status object. */ public Object convert(Object o) { return ((Map.Entry) o).getKey(); } public static final FrontFilterConverter INSTANCE = new FrontFilterConverter(); } // ----- inner class: HashcodeComparator -------------------------------- /** * A stateless Comparator that compares {@link Object#hashCode} values. */ protected static class HashcodeComparator implements Comparator { public int compare(Object o1, Object o2) { int n1 = o1 == null ? 0 : o1.hashCode(); int n2 = o2 == null ? 0 : o2.hashCode(); return n1 > n2 ? 1 : n1 == n2 ? 0 : -1; } @Override public boolean equals(Object obj) { return obj == this; } public static final HashcodeComparator INSTANCE = new HashcodeComparator(); } // ----- constants ------------------------------------------------------ /** * This event indicates that an entry has been added to the map. */ public static final int ENTRY_INSERTED = MapEvent.ENTRY_INSERTED; /** * This event indicates that an entry has been updated in the map. */ public static final int ENTRY_UPDATED = MapEvent.ENTRY_UPDATED; /** * This event indicates that an entry has been removed from the map. */ public static final int ENTRY_DELETED = MapEvent.ENTRY_DELETED; /** * An empty array of type Map Entry. */ static final Map.Entry[] ENTRY_ARRAY = new Map.Entry[0]; // ----- data fields ---------------------------------------------------- /** * The "front" map, which is size-limited. */ protected ObservableMap m_mapFront; /** * The "back" map, which the front overflows to. */ protected Map m_mapBack; /** * The miss cache; may be null. */ protected Map m_mapMiss; /** * An option to allow null values. */ private boolean m_fNullValuesAllowed; /** * An option to use putAll (no return value) to update the front Map. */ private boolean m_fUseFrontPutAll; /** * A Map for maintaining Status information on the entries that are being * managed by this Overflow Map. */ private Map m_mapStatus = new SafeHashMap(); /** * A list of keys that may have deferred events. */ private List m_listDeferred = new RecyclingLinkedList(); /** * A ThreadGate to coordinate key-level versus Map-level operations, and * potentially to detect re-entrancy. */ private Gate m_gate = new ThreadGateLite(); /** * The listener for the "front" map. */ private MapListener m_listenerFront; /** * The CacheStatistics object maintained by this cache. */ protected SimpleCacheStatistics m_stats = new SimpleCacheStatistics(); /** * Static flag used to make sure the same warning message (a missing event * or events out of order) is only issued once. */ private static boolean s_fWarnedEventSequence; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy