package org.jboss.cache.eviction;
import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
import org.jboss.cache.Fqn;
import org.jboss.cache.lock.TimeoutException;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.Map;
public class LRUAlgorithm implements EvictionAlgorithm
{
protected Logger log_ = Logger.getLogger(LRUAlgorithm.class);
protected Map nodeMap_ = new HashMap();
protected NodeEntry head_ = null;
protected NodeEntry tail_ = null;
protected Region region_;
private static final int CAPACITY = 50000;
protected BoundedBuffer recycleQueue_;
public LRUAlgorithm() {
recycleQueue_ = new BoundedBuffer(CAPACITY);
}
public void process(Region region) throws EvictionException
{
region_ = region;
if(log_.isDebugEnabled()) {
log_.debug("process(): process the sitting node events in region: " +region_.toString());
log_.debug("process(): current eviction queue size is " +evictionQueueSize());
}
processQueues();
if(log_.isDebugEnabled()) {
log_.debug("process(): trimming the nodes if ncessary ...");
}
prune();
}
public void resetEvictionQueue(Region region)
{
}
private void processQueues() throws EvictionException
{
boolean loop = true;
int count = 0;
while(loop) {
EvictedEventNode node = region_.takeLastEventNode();
if(node == null) { break;
}
Fqn fqn = node.getFqn();
Integer event = node.getEvent();
count++;
if(event.equals(EvictedEventNode.ADD_EVENT)) {
processAddedNodes(fqn);
} else if (event.equals(EvictedEventNode.REMOVE_EVENT)) {
processRemovedNodes(fqn);
} else if (event.equals(EvictedEventNode.VISIT_EVENT) ) {
processVisitedNodes(fqn);
} else {
throw new RuntimeException("LRUAlgorithm.processQueues(): Illegal event type " +event);
}
}
if(log_.isDebugEnabled()) {
log_.debug("processQueues(): process " +count + " node events");
}
}
private void processAddedNodes(Fqn fqn) throws EvictionException
{
long stamp = System.currentTimeMillis();
NodeEntry ne = new NodeEntry(fqn);
ne.setModifiedTimeStamp(stamp);
add(ne);
}
private void processRemovedNodes(Fqn fqn) throws EvictionException
{
removeFromQueue(fqn);
}
private void processVisitedNodes(Fqn fqn) throws EvictionException
{
NodeEntry ne = (NodeEntry)nodeMap_.get(fqn);
if(ne==null) { ne = new NodeEntry(fqn);
add(ne);
}
long stamp = System.currentTimeMillis();
ne.setModifiedTimeStamp(stamp);
demote(fqn);
}
private void add(NodeEntry ne) throws EvictionException
{
if( nodeMap_.containsKey(ne.getFqn()) ) {
demote(ne.getFqn()); return;
}
if(head_ == null && tail_ == null) {
head_ = ne;
tail_ = ne;
nodeMap_.put(ne.getFqn(), ne);
return; }
tail_.setNext(ne);
ne.setPrevious(tail_);
tail_ = ne;
nodeMap_.put(ne.getFqn(), ne);
}
protected void demote(Fqn fqn) throws EvictionException
{
NodeEntry ne = (NodeEntry)nodeMap_.get(fqn);
if( ne == null )
throw new EvictionException("LRUAlgorithm.demote(): internal error. Can't find fqn in nodeMap: " +fqn);
if(head_ == ne && tail_ == ne) { return;
}
if(tail_ == ne) return;
if(head_ == ne) { head_ = head_.getNext();
head_.setPrevious(null);
} else {
ne.getPrevious().setNext(ne.getNext());
ne.getNext().setPrevious(ne.getPrevious());
}
tail_.setNext(ne);
ne.setPrevious(tail_);
tail_ = ne;
ne.setNext(null);
}
private boolean evictCacheNode(Fqn fqn)
{
EvictionPolicy policy = region_.getEvictionPolicy();
try {
policy.evict(fqn);
} catch (Exception e) {
if( e instanceof TimeoutException) {
log_.warn("evictCacheNode(): evictCacheNode time out. Will try later.");
return false;
}
e.printStackTrace();
return false;
}
return true;
}
private void removeFromQueue(Fqn fqn) throws EvictionException
{
NodeEntry ne = (NodeEntry)nodeMap_.remove(fqn);
if( ne == null ) {
log_.warn("evict(): Node not found with this fqn: " +fqn + " Could have been evicted earlier already");
throw new EvictionException("LRUAlgorithm.removeFromQueue(): internal error. Can't find fqn in nodeMap. fqn: " +
fqn);
}
if(head_ == ne && tail_ == ne) { head_ = tail_ = null;
return;
}
if(head_ == ne) { head_ = head_.getNext();
head_.setPrevious(null);
ne.setNext(null);
return;
}
if(tail_ == ne) { tail_ = tail_.getPrevious();
tail_.setNext(null);
ne.setPrevious(null);
return;
}
ne.getPrevious().setNext(ne.getNext());
ne.getNext().setPrevious(ne.getPrevious());
}
protected void prune() throws EvictionException
{
boolean loop = true;
while(loop) {
NodeEntry ne = null;
try {
ne = (NodeEntry)recycleQueue_.poll(0);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
if(ne==null) break;
if(log_.isDebugEnabled()) {
log_.debug("prune(): emptying recycle bin. Evict node " +ne.getFqn());
}
if( !evictCacheNode(ne.getFqn()) ) {
try {
recycleQueue_.put(ne);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
while( region_.getMaxNodes() != 0 && region_.getMaxNodes() < nodeMap_.size() ) {
Fqn fqn = head_.getFqn();
if(log_.isDebugEnabled()) {
log_.debug("prune(): over-capacity. Evict node " +fqn);
}
evict(fqn);
if( head_ == null )
throw new EvictionException("LRUAlgorithm.prune(): internal error. head is null");
}
if(head_ == null) return; if(region_.getTimeToLiveSeconds() == 0) return; NodeEntry cursor = head_;
long currentTime = System.currentTimeMillis();
while(cursor != null) {
Fqn fqn = cursor.getFqn();
long idleTime = currentTime -cursor.getModifiedTimeStamp();
cursor = cursor.getNext();
if(idleTime >= (region_.getTimeToLiveSeconds() *1000) ) {
if(log_.isTraceEnabled()) {
log_.trace("prune(): over-aged. Evict node " +fqn);
}
evict(fqn);
}
}
}
protected void evict(Fqn fqn) throws EvictionException
{
NodeEntry ne = (NodeEntry)nodeMap_.get(fqn);
try {
removeFromQueue(fqn);
} catch (EvictionException e) {
e.printStackTrace();
return;
}
if( !evictCacheNode(fqn) ) {
try {
recycleQueue_.put(ne);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int evictionQueueSize() { return nodeMap_.size(); }
}