
com.bigdata.cache.HardReferenceQueue Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Nov 8, 2006
*/
package com.bigdata.cache;
/**
*
* A cache for hard references using an LRU policy. References are simply
* cached, but objects are not recoverable from their reference. In order to
* make an object recoverable, this cache must be wrapped by a weak reference
* cache that implements a hash map for discovery of objects using their
* persistent identifier. The {@link HardReferenceQueue} has a capacity that
* determines the #of hard references that may be cached before an object is
* evicted from the cache. Objects may be stored multiple times on the cache but
* the nscan most recent references are always tested before appending a
* reference to the cache in order to minimize cache churn when an object is
* touched repeatedly in close succession. Likewise, eviction does not mean that
* the object is no longer on the hard reference cache, nor does eviction mean
* that no hard references to the object exist within the VM. However, eviction
* is nevertheless used to drive persistence of the object. Since an object may
* be evicted multiple times without being updated in between evictions, this
* requires the object to implement a protocol for determining whether or not it
* is dirty. Eviction is then contingent on that protocol and the dirty state of
* the object is reset when it is serialized for eviction. In combination with
* the object's dirty protocol, the hard reference cache can substitute for a
* commit list.
*
*
* Note: This implementation is NOT synchronized.
*
*
* @author Bryan Thompson
* @version $Id$
*
* @param
* The reference type stored in the cache.
*/
public class HardReferenceQueue extends RingBuffer implements IHardReferenceQueue {
// protected static final Logger log = Logger.getLogger(HardReferenceQueue.class);
// protected static final boolean INFO = log.isInfoEnabled();
/**
* The listener to which cache eviction notices are reported.
*/
private final HardReferenceQueueEvictionListener listener;
/**
* The #of references to scan backwards from the LRU position when testing
* for whether or not a reference is already in the cache.
*/
protected final int nscan;
/**
* Uses the default #of references to scan on append requests.
*
* @param listener
* The listener on which cache evictions are reported.
* @param capacity
* The maximum #of references that can be stored on the cache.
* There is no guarantee that all stored references are distinct.
*/
public HardReferenceQueue(final HardReferenceQueueEvictionListener listener,
final int capacity) {
this(listener, capacity, Math.min(capacity, DEFAULT_NSCAN));
}
/**
* Fully specified ctor.
*
* @param listener
* The listener on which cache evictions are reported (optional).
* @param capacity
* The maximum #of references that can be stored on the cache.
* There is no guarantee that all stored references are distinct.
* @param nscan
* The #of references to scan from the MRU position before
* appended a reference to the cache. Scanning is used to reduce
* the chance that references that are touched several times in
* near succession from entering the cache more than once. The
* #of reference tests trades off against the latency of adding a
* reference to the cache.
*/
public HardReferenceQueue(
final HardReferenceQueueEvictionListener listener,
final int capacity, final int nscan) {
super(capacity);
// if (listener == null)
// throw new IllegalArgumentException();
if (nscan < 0 || nscan > capacity)
throw new IllegalArgumentException();
this.listener = listener;
this.nscan = nscan;
}
/**
* The listener specified to the constructor.
*/
final public HardReferenceQueueEvictionListener getListener() {
return listener;
}
/**
* The #of references that are tested on append requests.
*/
final public int nscan() {
return nscan;
}
/**
* Add a reference to the cache. If the reference was recently added to the
* cache then this is a NOP. Otherwise the reference is appended to the
* cache. If a reference is appended to the cache and then cache is at
* capacity, then the LRU reference is first evicted from the cache.
*
* @param ref
* The reference to be added.
*
* @return True iff the reference was added to the cache and false iff the
* reference was found in a scan of the nscan MRU cache entries.
*/
@Override
public boolean add(final T ref) {
// note: tested by the base class in offer().
// if (ref == null)
// throw new IllegalArgumentException();
/*
* Scan the last nscan references for this reference. If found, return
* immediately.
*/
if (nscan > 0 && scanHead(nscan, ref)) {
return false;
}
super.add(ref);
return true;
}
/**
* Extended to evict the element at the tail of the buffer iff the buffer is
* full.
*
* Note: This hook is further extended to realize the stale reference
* protocol in {@link SynchronizedHardReferenceQueueWithTimeout}.
*/
protected void beforeOffer(final T ref) {
// super.beforeOffer(ref); // base class is NOP.
if (size == capacity/* isFull() inline */) {
/*
* If at capacity, evict the LRU reference.
*/
evict();
}
}
/**
* Evict the LRU reference. This is a NOP iff the cache is empty.
*
* @return true iff a reference was evicted.
*
* @see HardReferenceQueueEvictionListener
*/
final public boolean evict() {
final T ref = poll();
if (ref == null) {
// buffer is empty.
return false;
}
if (listener != null) {
// report eviction notice to listener.
listener.evicted(this, ref);
}
return true;
}
/**
* Evict all references, starting with the LRU reference and proceeding to
* the MRU reference.
*
* @param clearRefs
* When true, the reference are actually cleared from the cache.
* This may be false to force persistence of the references in
* the cache without actually clearing the cache.
*/
final public void evictAll(final boolean clearRefs) {
if (clearRefs) {
/*
* Evict all references, clearing each as we go.
*/
while (!isEmpty()) { // count > 0 ) {
evict();
}
} else {
/*
* Generate eviction notices in LRU to MRU order but do NOT clear
* the references.
*/
final int size = size();
for (int n = 0; n < size; n++) {
final T ref = get(n);
if (listener != null) {
// report eviction notice to listener.
listener.evicted(this, ref);
}
}
// /*
// * Note: This uses local variables to shadow the instance variables
// * so that we do not modify the state of the cache as a side effect.
// */
//
// int tail = this.tail;
//
// int count = this.size;
//
// while (count > 0) {
//
// final T ref = refs[tail]; // LRU reference.
//
// count--; // update #of references.
//
// tail = (tail + 1) % capacity(); // update tail.
//
// if (listener != null) {
//
// // report eviction notice to listener.
// listener.evicted(this, ref);
//
// }
//
// }
}
}
}