com.bigdata.cache.SynchronizedHardReferenceQueueWithTimeout Maven / Gradle / Ivy
Show all versions of bigdata-cache Show documentation
/*
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 Feb 9, 2009
*/
package com.bigdata.cache;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.bigdata.util.DaemonThreadFactory;
/**
*
* Thread-safe version with timeout for clearing stale references from the
* queue. Clearing of stale entries is accomplished when a value is added to the
* {@link SynchronizedHardReferenceQueue}. The tail of the queue is tested and
* any entry on the tail whose age exceeds a timeout is evicted. This continues
* until we reach the first value on the tail of the queue whose age is greater
* than the timeout. This behavior is enabled if a non-ZERO timeout is
* specified. Stales references are also cleared by a background thread.
*
*
* @author Bryan Thompson
* @version $Id: SynchronizedHardReferenceQueueWithTimeout.java 4410 2011-04-17
* 20:11:44Z thompsonbry $
*/
public class SynchronizedHardReferenceQueueWithTimeout implements
IHardReferenceQueue {
private static final Logger log = Logger
.getLogger(SynchronizedHardReferenceQueueWithTimeout.class);
/**
* Note: Synchronization for the inner {@link #queue} is realized using the
* outer reference!
*/
private final InnerHardReferenceQueue> queue;
/**
* Package private access in support of the unit tests.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
final HardReferenceQueue> getQueue() {
return (HardReferenceQueue) queue;
}
/**
* Variant with no listener and timeout.
*
* @param capacity
* The maximum #of references that can be stored on the cache.
* There is no guarantee that all stored references are distinct.
* @param timeoutNanos
* The timeout (in nanoseconds) for an entry in the queue. When
* ZERO (0L), the timeout is disabled.
*/
public SynchronizedHardReferenceQueueWithTimeout(final int capacity,
final long timeoutNanos) {
this(capacity, DEFAULT_NSCAN, timeoutNanos);
}
/**
* Optional timeout.
*
* @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.
* @param timeoutNanos
* The timeout (in nanoseconds) for an entry in the queue. When
* ZERO (0L), the timeout is disabled.
*/
public SynchronizedHardReferenceQueueWithTimeout(final int capacity,
final int nscan, final long timeoutNanos) {
this(null/* listener */, capacity, nscan, timeoutNanos);
}
/*
* package private constructor for unit tests.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
SynchronizedHardReferenceQueueWithTimeout(
final HardReferenceQueueEvictionListener> listener,
final int capacity, final int nscan, final long timeoutNanos) {
this.queue = new InnerHardReferenceQueue>(
(HardReferenceQueueEvictionListener) listener, capacity, nscan,
timeoutNanos);
if (queue.timeout > 0) {
queues.add(new WeakReference(
this));
}
}
/**
* Inner class wraps each object inserted into the queue with the nanotime
* corresponding to that insert. A {@link Cleaner} thread runs periodically
* and removes stale references from the tail of the queue.
*
* Note: This deliberately DOES NOT test the tail in
* {@link HardReferenceQueue#beforeOffer(Object)} to cut down on the
* overhead associated with {@link #add(Object)}. Stale references will be
* evicted regardless when the {@link Cleaner} runs.
*
* @author Bryan
* Thompson
* @param
* The generic type of the queue entries for this inner class.
*/
static private class InnerHardReferenceQueue> extends
HardReferenceQueue {
/**
* The timeout (in nanoseconds) for an entry in the queue. When ZERO
* (0L), the timeout is disabled.
*/
private final long timeout;
/**
*
* @param listener
* @param capacity
* @param nscan
* @param timeout
* The timeout (in nanoseconds) for an entry in the queue.
* When ZERO (0L), the timeout is disabled.
*/
public InnerHardReferenceQueue(
final HardReferenceQueueEvictionListener listener,
final int capacity, final int nscan, final long timeout) {
super(listener, capacity, nscan);
if (timeout < 0)
throw new IllegalArgumentException();
this.timeout = timeout;
}
/**
* Examine references backwards from the tail, evicting any that have
* become stale (too long since they were last touched).
*
* @param timeout
* The timeout in nanoseconds.
*
* @return The #of stale references which were cleared.
*/
int evictStaleRefs(final long timeout) {
final int size0 = size();
if (size0 == 0)
return 0;
final long now = System.nanoTime();
final long maxAge = now - peek().ts;
T x;
long age = 0;
int ncleared = 0;
while ((x = peek()) != null) {
age = now - x.ts;
if (age < timeout)
break;
// evict the tail.
evict();
if (log.isTraceEnabled())
log.trace("Evicting: " + x.ref + " : timeout="
+ TimeUnit.NANOSECONDS.toMillis(timeout)
+ "ms, age=" + TimeUnit.NANOSECONDS.toMillis(age)
+ "ms, size=" + size + ", ncleared=" + ncleared);
ncleared++;
}
if (log.isDebugEnabled() && ncleared > 3)
log.debug("#ncleared=" + ncleared + ", size=" + size()
+ ", timeout=" + TimeUnit.NANOSECONDS.toMillis(timeout)
+ ", maxAge=" + TimeUnit.NANOSECONDS.toMillis(maxAge)
+ ", age=" + TimeUnit.NANOSECONDS.toMillis(age));
return ncleared;
}
/**
* Examine references backwards from the tail, evicting any that have
* become stale (too long since they were last touched) based on the
* timeout specified to the ctor (this is a NOP if the timeout is
* ZERO(0)).
*/
public int evictStaleRefs() {
if (timeout != 0L) {
return evictStaleRefs(timeout);
}
return 0;
}
/**
* {@inheritDoc}
*
* Overridden to handle the indirection from the {@link ValueAge} object
* to the wrapped reference. It is the wrapped references that we need
* to test for reference equality.
*
* @see
* Too many GRS reads
*/
@Override
final public boolean scanHead(final int nscan, final T ref) {
if (nscan <= 0)
throw new IllegalArgumentException();
if (ref == null)
throw new IllegalArgumentException();
/*
* Note: This loop goes backwards from the head. Since the head is the
* insertion point, we decrement the head position before testing the
* reference. If the head is zero, then we wrap around. This carries
* the head back to the last index in the array (capacity-1).
*
* 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 head = this.getHeadIndex();
int count = this.size;
// unwrap the caller's reference.
final Object o1 = ref.get();
for (int i = 0; i < nscan && count > 0; i++) {
head = (head == 0 ? capacity - 1 : head - 1); // update head.
count--; // update #of references.
// Unwrap the reference at this position in the ring buffer.
final Object o2 = _get(head).get();// refs[head]
if (o1 == o2) {
// Found a match.
return true;
}
}
return false;
}
}
/*
* Methods which DO NOT require synchronization.
*/
/**
* The timeout (in nanoseconds) for an entry in the queue. When ZERO (0L),
* the timeout is disabled.
*/
final public long timeout() {
return queue.timeout;
}
final public int capacity() {
return queue.capacity();
}
final public int nscan() {
return queue.nscan();
}
/*
* Methods which DO require synchronization.
*/
/*
* Note: I've tried coding add(ref) two different ways here. Probably this
* is too small an issue to make a different and the runs may have been too
* short, plus the performance could vary by hotspot compiler.
*
* Another alternative, which would break encapsulation, is to have a
* parallel array of long timestamps for the references so we can avoid
* the allocation entirely.
*/
// // allocate ValueAge outside of synchronized block: 3156/1656 on U10 query.
// public boolean add(final T ref) {
//
// final ValueAge v = new ValueAge(ref);
// synchronized(this) {
//
// return queue.add(v);
//
// }
//
// }
// allocate inside of synchronized block: 2781/406 on U10 query.
synchronized public boolean add(final T ref) {
if(ref == null)
throw new IllegalArgumentException();
return queue.add(new ValueAge(ref));
}
synchronized public void clear(final boolean clearRefs) {
queue.clear(clearRefs);
}
synchronized public boolean evict() {
return queue.evict();
}
synchronized public void evictAll(final boolean clearRefs) {
queue.evictAll(clearRefs);
}
synchronized public T peek() {
final ValueAge age = queue.peek();
return age == null ? null : age.ref;
}
synchronized public boolean isEmpty() {
return queue.isEmpty();
}
synchronized public boolean isFull() {
return queue.isFull();
}
synchronized public int size() {
return queue.size();
}
/*
* Cleaner service.
*/
/**
* This method may be invoked by life cycle operations which need to tear
* down the bigdata environment. Normally you do not need to do this as the
* cleaner service uses a daemon thread and will not prevent the JVM from
* halting. However, a servlet container running bigdata can complain that
* some threads were not terminated if the webapp running bigdata is
* stopped. You can invoke this method to terminate the stale reference
* cleaner thread.
*/
public static final void stopStaleReferenceCleaner() {
cleanerService.shutdownNow();
}
private static final ScheduledExecutorService cleanerService;
static {
cleanerService = Executors
.newSingleThreadScheduledExecutor(new DaemonThreadFactory(
"StaleReferenceCleaner"));
cleanerService.scheduleWithFixedDelay(new Cleaner(),
5000/* initialDelay */, 5000/* delay */, TimeUnit.MILLISECONDS);
}
/**
* Collection of weak references to {@link SynchronizedHardReferenceQueue}s
* with non-zero timeouts to be processed by the cleaner.
*
* Note: a collection of {@link WeakReference}s so it does not force the
* retention of the {@link SynchronizedHardReferenceQueue} objects by
* itself.
*/
private static ConcurrentLinkedQueue> queues = new ConcurrentLinkedQueue>();
/**
* Cleans stale references from all known
* {@link SynchronizedHardReferenceQueue} instances having a non-ZERO
* timeout.
*
* @author Bryan Thompson
*/
private static class Cleaner implements Runnable {
/**
* Each run clears stale references from each existing
* {@link SynchronizedHardReferenceQueue} instance. This method is also
* responsible for removing {@link WeakReference}s which have been
* cleared from {@link SynchronizedHardReferenceQueue#clients}.
*
* Note: All exceptions are caught and logged since any exception thrown
* form here would terminate the scheduled execution of this task.
*/
public void run() {
try {
int ncleared = 0;
int nqueues = 0;
final Iterator> itr = queues
.iterator();
while (itr.hasNext()) {
final WeakReference ref = itr.next();
final SynchronizedHardReferenceQueueWithTimeout queue = ref.get();
if (queue == null) {
itr.remove();
continue;
}
/*
* Note: Synchronization is imposed on the outer class
* reference.
*/
synchronized (queue) {
ncleared += ((InnerHardReferenceQueue) queue.queue)
.evictStaleRefs();
}
nqueues++;
}
if (ncleared > 0 && log.isInfoEnabled())
log.info("Cleared " + ncleared + " stale references from "
+ nqueues + " queues");
} catch (Throwable t) {
log.error(t, t);
}
}
}
/**
* Interface for something wrapping a reference.
*
* @author Bryan Thompson
* @param
*/
public static interface IRef {
T get();
}
/**
* Wraps an object so that it can self-report the last timestamp when it was
* last accessed.
*
* @author Bryan Thompson
* @param
* The generic type of the wrapped object.
*/
static class ValueAge implements IRef {
/**
* The object stored in the queue.
*/
/*private*/ final T ref;
public T get() {
return ref;
}
/**
* The timestamp associated with the value. This is initialized to the
* {@link System#nanoTime()} when the {@link ValueAge} was created and
* is updated to the value of {@link System#nanoTime()} each time
* {@link #touch()} is invoked.
*/
final /*private*/ long ts = System.nanoTime();
public ValueAge(final T ref) {
this.ref = ref;
}
}
}