Package nux.xom.pool

Source Code of nux.xom.pool.Pool$SoftLRUHashMap

/*
* Copyright (c) 2005, The Regents of the University of California, through
* Lawrence Berkeley National Laboratory (subject to receipt of any required
* approvals from the U.S. Dept. of Energy). All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* (1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* (3) Neither the name of the University of California, Lawrence Berkeley
* National Laboratory, U.S. Dept. of Energy nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* You are under no obligation whatsoever to provide any bug fixes, patches, or
* upgrades to the features, functionality or performance of the source code
* ("Enhancements") to anyone; however, if you choose to make your Enhancements
* available either publicly, or directly to Lawrence Berkeley National
* Laboratory, without imposing a separate written license agreement for such
* Enhancements, then you hereby grant the following license: a non-exclusive,
* royalty-free perpetual license to install, use, modify, prepare derivative
* works, incorporate into other computer software, distribute, and sublicense
* such enhancements or derivative works thereof, in binary and source code
* form.
*/
package nux.xom.pool;

import java.io.File;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.lucene.index.memory.MemoryIndex;

import nu.xom.Node;

/**
* Thread-safe memory sensitive cache/pool using a LRUHashMap; maintains a
* {@link SoftReference} for each map value; Automatically and safely evicts
* stale entries of values that have been garbage collected by Java's soft
* reference mechanism.
* <p>
* Null keys are permitted. Null values are permitted but their mappings are
* silently removed (a cache with null values is rather meaningless).
* <p>
* For a discussion of atomic updates, visibility and ordering in the Java
* memory model see http://gee.cs.oswego.edu/dl/cpj/jmm.html
*
* @see nux.xom.sandbox.DocumentMapTest
*
* @author whoschek.AT.lbl.DOT.gov
* @author $Author: hoschek3 $
* @version $Revision: 1.31 $, $Date: 2006/05/04 06:03:52 $
*/
final class Pool implements Map { // not a public class!

  /** the underlying map holding the real entries */
  private final SoftLRUHashMap child;
//  private final Map child;

  /** gc notifies us of soft ref collections by enqueuing onto this queue */
  private final ReferenceQueue queue;
 
  /**
   * the current amount of memory [bytes] this map occupies
   */
  private long totalSize;

  private final long maxIdleTime; // copied from config
  private final long maxLifeTime; // copied from config
  private final long capacity;    // copied from config
  private final boolean fileMonitoring; // copied from config

  /**
   * daemon thread running periodically to evict invalid entries.
   */
  private static final Timer SWEEPER = new Timer(true);

  /** enable Nux debug output on System.err? */
  private static final boolean DEBUG =
    XOMUtil.getSystemProperty("nux.xom.pool.Pool.debug", false);

 
  /** Returns a wrapper around an array to be used as a key in a HashMap */
  static Object createHashKeys(Object[] keys) {
    return new HashKeys(keys);
  }
 
  /**
   * Constructs a new instance with the given parameters.
   *
   * @param config
   *            the configuration to use
   */
  static Map createPool(PoolConfig config) {
    if (config == null)
      throw new IllegalArgumentException("config must not be null");
   
    int maxEntries = config.getMaxEntries();
    if (config.getCapacity() <= 0 || config.getMaxIdleTime() <= 0 || config.getMaxLifeTime() <= 0) {
      maxEntries = 0; // avoid unnecessary overhead
    }
    if (maxEntries > 0)
      return new Pool(config);
    else
      return Collections.synchronizedMap(new LRUHashMap(Math.abs(maxEntries)));
  }
 
  private Pool(PoolConfig config) {   
    this.totalSize = 0;
    this.child = new SoftLRUHashMap(this, config.getMaxEntries());
    this.capacity = config.getCapacity();
    this.fileMonitoring = config.getFileMonitoring();

    // fixup terribly inefficient parameters
    this.maxIdleTime = Math.max(100, config.getMaxIdleTime());
    this.maxLifeTime = Math.max(100, config.getMaxLifeTime());
    long t = Math.min(maxIdleTime, maxLifeTime);

    t = Math.min(config.getInvalidationPeriod(), t);
    if (t == Long.MAX_VALUE) t = -1; // never evict
    if (t >= 0) t = Math.max(100, t); // fixup inefficient parameters
    this.queue = t > 0 ? new ReferenceQueue() : null;
    if (t > 0) SWEEPER.schedule(new SweepTask(this), t, t);
  }

  public synchronized void clear() {
    evictStaleEntries();
    child.clear();
    totalSize = 0;
  }

  public synchronized Object get(Object key) {
    evictStaleEntries();
    SoftValue ref = (SoftValue) child.get(key);
    return SoftValue.unwrap(ref, false);
  }

  public Object put(Object key, Object value) {
    synchronized (this) {
      evictStaleEntries();
    }
   
    int size = 0;
    if (value != null && capacity != Long.MAX_VALUE) {
      int valueSize = getMemorySize(value); // need not hold lock (potentially expensive)
      int keySize = getMemorySize(key);
      size += valueSize + keySize;
//      if (DEBUG) System.err.println("vsize=" + valueSize + ", ksize=" + keySize + ", MB=" + (totalSize / (1024.0f * 1024.0f)));
      if (size > capacity) value = null; // i.e. remove entry, if any
    }
   
    synchronized (this) {
      SoftValue old;
      if (value != null) {
        SoftValue ref = new SoftValue(key, value, queue, size);
        old = (SoftValue) child.put(key, ref);
        totalSize += size;
      } else {
        old = (SoftValue) child.remove(key);
      }
 
      Object result = SoftValue.unwrap(old, true);
      if (old != null) evictEntry(key, old, "PUT");
      if (value != null && totalSize > capacity) evictExcessMemoryEntries();
      return result;
    }
  }

  public Object remove(Object key) {
    return put(key, null);
  }

  // methods that are not really needed for our purposes:
  public synchronized boolean containsKey(Object key) { return get(key) != null; }
  public synchronized int size() { return child.size(); }
  public synchronized boolean isEmpty() { return child.isEmpty(); }
  public synchronized Set keySet() { return child.keySet(); }
  public void putAll(Map src) {
    Iterator iter = src.entrySet().iterator();
    while (iter.hasNext()) {
      Entry entry = (Entry) iter.next();
      put(entry.getKey(), entry.getValue());
    }
  }

  // methods that could be implemented; so far not needed at all for our purposes:
  public boolean containsValue(Object value) { throw new UnsupportedOperationException(); }
  public Collection values() { throw new UnsupportedOperationException(); }
  public Set entrySet() { throw new UnsupportedOperationException(); }

  /** mark entry as removed; the actual removal is done elsewhere */
  private void evictEntry(Object key, SoftValue ref, String msg) {
    ref.remove();
    totalSize -= ref.size;
    if (DEBUG) {
      String str = String.valueOf(key);
      if (str.length() > 35) str = str.substring(0, 32) + "...";
      System.err.println("*******" + msg + " EVICTED=" + str
          + ", size()=" + child.size() + ", MB=" + (totalSize / (1024.0f * 1024.0f)));
    }
//    if (key instanceof PoolValidatingKey) { // TODO: consider adding feature?
//      ((PoolValidatingKey) key).onRemoval(); // notify for potential dependency chain invalidation
//    }
  }
 
  /** removes all entries that have been collected and enqueued by the VM gc */
  private void evictStaleEntries() {
    if (queue == null) return; // nothing to do
    SoftValue ref;
    while ((ref = (SoftValue) queue.poll()) != null) {
      if (!ref.isRemoved()) {
        child.remove(ref.key);
        evictEntry(ref.key, ref, "GC");
      }
    }
  }

  /** removes all entries that turn out to be nomore valid (to be run periodically) */
  private void evictInvalidEntries() {
    Iterator iter = child.entrySet().iterator();
    long now = System.currentTimeMillis();
    long idle = maxIdleTime - now;
    long life = maxLifeTime - now;
   
    while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      Object key = entry.getKey();
      SoftValue ref = (SoftValue) entry.getValue();
      String msg = null;
     
      boolean isValid = ref.lastAccessTime + idle > 0;
      if (!isValid) msg = "INVALID (maxIdleTime)";
     
      if (isValid) {
        isValid = ref.insertionTime + life > 0;
        if (!isValid) msg = "INVALID (maxLifeTime)";
      }
     
      if (isValid && key instanceof PoolValidatingKey) {
        isValid = ((PoolValidatingKey) key).isValid();
        if (!isValid) msg = "INVALID (PoolValidatingKey)";
      }
     
      if (isValid && fileMonitoring) {
        File file = null;
        if (key instanceof File) {
          file = (File) key;
        } else if (key instanceof HashKeys) {
          file = ((HashKeys) key).getFile();
        }
       
        if (file != null) { // invalidate entry if it's file has changed
          long lastModified = file.lastModified();
          isValid = lastModified != 0 && lastModified <= ref.insertionTime;
          if (!isValid) msg = "INVALID (fileChange)";
        }
      }
     
      if (!isValid) {
        iter.remove();
        evictEntry(key, ref, msg);
      }     
    }   
  }

  /** removes LRU entries until the max memory limit invariant holds again */
  private void evictExcessMemoryEntries() {   
    Iterator iter = child.entrySet().iterator();
    while (totalSize > capacity && iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      iter.remove();
      evictEntry(entry.getKey(), (SoftValue) entry.getValue(), "MEMORY");
    }
  }

  /** calculates approximate memory consumption of the given value object */
  private static int getMemorySize(Object value) {
    if (value == null)
      return 0;
    if (value instanceof byte[])
      return ((byte[]) value).length;
    if (value instanceof Node)
      return XOMUtil.getMemorySize((Node) value);
    if (value instanceof CharSequence)
      return 2 * ((CharSequence) value).length();
   
    if (value instanceof HashKeys) {
      value = ((HashKeys) value).keys;
    }
    else if (value instanceof Collection) {
      value = ((Collection) value).toArray();
    }
   
    if (value instanceof Object[]) {
      Object[] arr = (Object[]) value;
      int size = 12 + 4 + 4 * arr.length;
      for (int i=arr.length; --i >= 0; ) {
        size += getMemorySize(arr[i]); // assumes no graph cycles
      }
      return size;
    }   
    else if (value instanceof MemoryIndex) {
      return ((MemoryIndex) value).getMemorySize();
    }
   
    return 0;
  }
 

  ///////////////////////////////////////////////////////////////////////////////
  // Nested classes:
  ///////////////////////////////////////////////////////////////////////////////

  /**
   * Bounded LinkedHashMap with least-recently-used (LRU) eviction policy.
   */
  private static class LRUHashMap extends LinkedHashMap {
   
    private final int maxSize;

    private LRUHashMap(int maxSize) {
      super(1, 0.75f, true);
      this.maxSize = maxSize;
    }
   
    protected boolean removeEldestEntry(Map.Entry eldest) {
      return size() > maxSize;
    }
     
  } 
 
  /**
   * Notifies the Pool of removals that occur as a result of exceeding the
   * maxEntries limit. This helps to accurately keep track of the current
   * total memory size occupied by the entries.
   */
  private static final class SoftLRUHashMap extends LRUHashMap {
   
    private final Pool pool;
   
    private SoftLRUHashMap(Pool pool, int maxEntries) {
      super(maxEntries);
      this.pool = pool;
    }
   
    protected boolean removeEldestEntry(Map.Entry eldest) {
      if (super.removeEldestEntry(eldest)) {
        remove(eldest.getKey());
        pool.evictEntry(eldest.getKey(), (SoftValue) eldest.getValue(), "MAXENTRIES");
      }
      return false;
    }
  }
 
  /**
   * A SoftReference that remembers the entry's key for safe and efficient
   * removal on evictStaleEntries()
   */
  private static final class SoftValue extends SoftReference {

    private Object key; // hard ref to the key this value is associated with
    private final long insertionTime; // timestamp at put()
    private long lastAccessTime; // timestamp on get()
    private final int size; // memory consumed by the value [bytes]
    private static final Object REMOVED = new Object(); // marker

    private SoftValue(Object key, Object value, ReferenceQueue queue, int size) {
      super(value, queue);
      this.key = key;
      this.insertionTime = System.currentTimeMillis();
      this.lastAccessTime = this.insertionTime;
      this.size = size;
    }

    private static Object unwrap(SoftValue ref, boolean remove) {
      if (ref == null) return null;
      Object value = ref.get();
      if (remove) {
        ref.remove();
      } else {
        ref.lastAccessTime = System.currentTimeMillis();
      }
      return value;
    }

    private boolean isRemoved() {
      return key == REMOVED;
    }

    private void remove() {
      clear();
      key = REMOVED;
    }

    public String toString() { // for debug only
      return "key=" + key + ", val=" + get();
    }
  }

  /**
   * Removes stale entries from the given Pool while making sure not to
   * prevent the Pool itself from being garbage collected.
   */
  private static final class SweepTask extends TimerTask {

    private final WeakReference poolRef;

    private SweepTask(Pool pool) {
      this.poolRef = new WeakReference(pool);
    }

    public void run() {
      try {
        Pool pool = (Pool) poolRef.get();
        if (pool != null) {
          if (DEBUG) {
            System.err.println("############### Pool.SweepTask running...");
          }
          long now = DEBUG ? System.currentTimeMillis() : 0;
          synchronized (pool) { // must hold lock
            pool.evictStaleEntries();
            pool.evictInvalidEntries();
          }
          if (DEBUG) System.err.println("Pool.SweepTask took ms=" +
              (System.currentTimeMillis() - now));
        } else {
          cancel(); // pool has been gc'd; remove task from timer
        }
      } catch (Throwable t) { // keep sweeper operational no matter what
        t.printStackTrace();
        if (DEBUG) System.exit(-1); // TODO: remove this when building a release?
      }
    }
  }

  /**
   * Small efficient helper wrapping an array to be used as a key in a HashMap;
   * intended for caches/pools.
   * <p>
   * The key array can contain null elements.
   * Avoids slow iteration for equals() and hashCode()
   * in AbstractList and hence java.util.Arrays.asList().
   */
  private static final class HashKeys {

    private final Object[] keys;
   
    private HashKeys(Object[] keys) {
      if (keys == null) throw new IllegalArgumentException("keys must not be null");
      this.keys = keys;
    }
   
    public final boolean equals(Object other) {
      if (other instanceof HashKeys) {
        return eq(keys, ((HashKeys) other).keys);
      }
      return false;
    }
   
    public final int hashCode() { // see java.util.Arrays.asList().hashCode()
      int hash = 1;
      Object[] k = keys;
      for (int i = k.length; --i >= 0; ) {
        hash *= 31;
        if (k[i] != null) hash += k[i].hashCode();
      }
      return hash;
    }
   
    private static boolean eq(Object[] k1, Object[] k2) {
      int len = k1.length;
      if (len != k2.length) return false;
      for (int i = 0; i < len; i++) {
        Object x = k1[i];
        Object y = k2[i];
        if (x != y && (x == null || y == null || !x.equals(y))) {
          return false;
        }
      }
      return true;     
    }
   
    /** see evictInvalidEntries() and XQueryPool.getXQuery(File, URI) */
    public File getFile() {
      if (keys.length > 0 && keys[0] instanceof File) {
        return (File) keys[0];
      }
      return null;
    }
   
    public String toString() { // for debug only
      return Arrays.asList(keys).toString();
    }

  }
}
TOP

Related Classes of nux.xom.pool.Pool$SoftLRUHashMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.