/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.cache;
import java.util.HashMap;
import java.util.Iterator;
import org.hypergraphdb.HGAtomAttrib;
import org.hypergraphdb.HGAtomCache;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGPersistentHandle;
import org.hypergraphdb.HGSystemFlags;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.IncidenceSet;
import org.hypergraphdb.handle.DefaultManagedLiveHandle;
import org.hypergraphdb.handle.HGLiveHandle;
import org.hypergraphdb.util.ActionQueueThread;
import org.hypergraphdb.event.HGAtomEvictEvent;
/**
* <p>
* A default, simple implementation of a run-time cache of hypergraph atoms.
* </p>
*
* <p>
* <strong>IMPLEMENTATION NOTE:</strong> This implementation maintains usage statistics
* (access count and last time of access), calculates an importance function based on
* those statistics and maintains a priority queue based on that importance. This incurs
* a significant memory overhead on a per atom basis. But the importance-based eviction
* policy is quite accurate. This implementation makes more sense when atoms are relatively
* large, but we need to come up and experiment with other schemas for cache maintenance.
* Something based on Java weak reference might work well, for example.
* </p>
*
* TODO: this implementation is NOT thread-safe. Mutexes need to be inserted at various
* cache manipulation points.
*
* @author Borislav Iordanov
*/
public final class DefaultAtomCache implements HGAtomCache
{
private static class LiveHandle extends DefaultManagedLiveHandle
{
LiveHandle next, prev;
public LiveHandle(Object ref,
HGPersistentHandle pHandle,
byte flags)
{
super(ref, pHandle, flags, 1L, System.currentTimeMillis());
}
public LiveHandle(Object ref,
HGPersistentHandle pHandle,
byte flags,
long retrievalCount,
long lastAccessTime)
{
super(ref, pHandle, flags, retrievalCount, System.currentTimeMillis());
}
void setRef(Object ref)
{
this.ref = ref;
}
}
private HyperGraph hg;
/**
* HGPersistentHandle -> LiveHandle
*/
private final HashMap<HGPersistentHandle, LiveHandle>
liveHandles = new HashMap<HGPersistentHandle, LiveHandle>();
/**
* Object -> LiveHandle
*/
private final HashMap<Object, LiveHandle>
atoms = new HashMap<Object, LiveHandle>();
/**
* HGPersistentHandle -> incidence set
*/
private HGCache<HGPersistentHandle, IncidenceSet> incidenceSets = null;
private long retrievalCount = 0;
private long lastAccessTime = System.currentTimeMillis();
private double retrievalFrequencyWeight = 10.0;
private double lastAccessTimeWeight = 1.0;
private LiveHandle atomQueueTail = null;
private ActionQueueThread queueThread = null;
//
// Configuration parameters.
//
private long maxAtoms = 100;
private long maxIncidenceSets = 10;
public double importanceOf(LiveHandle cached)
{
return retrievalFrequencyWeight*((double)cached.getRetrievalCount() / (double)retrievalCount) +
lastAccessTimeWeight*((double)cached.getLastAccessTime() / (double)lastAccessTime);
}
private void importanceUp(LiveHandle cached)
{
double importance = importanceOf(cached);
while (cached.next != null &&
importance > importanceOf(cached.next))
{
//
// This looks a bit incomprehensible: draw 4 boxes in line
// with next and prev pointers and go through the exercise
// of rearranging the pointers so that the second box should
// move before the fourth. That's moving an element up the queue.
//
// If it is common that elements suddenly jump a lot in importance
// so that this iteration is repeated more than a couple of times,
// perhaps a more efficient loop where the destination is first
// determined would be better. An insertion of a new element into
// the cache is one such case, but presumably insertions are rare
// compared to access.
//
cached.next.prev = cached.prev;
if (cached.prev != null)
cached.prev.next = cached.next;
cached.prev = cached.next;
if (cached.next.next != null)
cached.next.next.prev = cached;
cached.next = cached.next.next;
cached.prev.next = cached;
if (atomQueueTail == cached)
atomQueueTail = cached.prev;
}
}
private void insert(LiveHandle handle)
{
//
// Always add a newly read atom to the cache. But free up some space if we've
// used it all.
//
if (liveHandles.size() >= maxAtoms)
{
//
// We must pay attention not to generate to many evict actions and
// give the chance to eviction to actually occur. So we block until all scheduled
// queue maintanance actions have been completed.
//
queueThread.addAction(new AtomsEvictAction(liveHandles.size() / 10));
queueThread.setPriority(Thread.NORM_PRIORITY + 2);
// queueThread.completeAll();
}
liveHandles.put(handle.getPersistent(), handle);
atoms.put(handle.getRef(), handle);
queueThread.addAction(new AddAtomAction(handle));
}
public DefaultAtomCache()
{
queueThread = CacheActionQueueSingleton.get();
}
public void setIncidenceCache(HGCache<HGPersistentHandle, IncidenceSet> cache)
{
this.incidenceSets = cache;
}
public HGCache<HGPersistentHandle, IncidenceSet> getIncidenceCache()
{
return incidenceSets;
}
public void setMaxAtoms(long maxAtoms)
{
this.maxAtoms = maxAtoms;
}
public long getMaxAtoms()
{
return maxAtoms;
}
public void setMaxIncidenceSets(long maxIncidenceSets)
{
this.maxIncidenceSets = maxIncidenceSets;
}
public long getMaxIncidenceSets()
{
return maxIncidenceSets;
}
public void setHyperGraph(HyperGraph hg)
{
this.hg = hg;
}
public void close()
{
for (Iterator<LiveHandle> i = liveHandles.values().iterator(); i.hasNext(); )
{
HGLiveHandle lHandle = (HGLiveHandle)i.next();
hg.getEventManager().dispatch(hg, new HGAtomEvictEvent(lHandle, lHandle.getRef()));
}
liveHandles.clear();
atoms.clear();
incidenceSets.clear();
atomQueueTail = null;
queueThread.stopRunning();
}
/**
* <p>Lookup in the cache for a live handle corresponding to a persistent
* handle.</p>
*/
public HGLiveHandle get(final HGPersistentHandle pHandle)
{
LiveHandle result = liveHandles.get(pHandle);
if (result == null)
return null;
result.accessed();
retrievalCount++;
lastAccessTime = System.currentTimeMillis();
queueThread.addAction(new AtomAccessedAction(result));
return result;
}
/**
* <p>Retrieve the live handle of an atom instance.</p>
*/
public HGLiveHandle get(final Object atom)
{
LiveHandle result = atoms.get(atom);
if (result == null)
return null;
result.accessed();
retrievalCount++;
lastAccessTime = System.currentTimeMillis();
queueThread.addAction(new AtomAccessedAction(result));
return result;
}
public HGLiveHandle atomAdded(final HGPersistentHandle pHandle, final Object atom, final HGAtomAttrib attrib)
{
return atomRead(pHandle, atom, attrib);
}
/**
* <p>Associate an atom instance and a persistent handle with a live handle.</p>
*/
public HGLiveHandle atomRead(final HGPersistentHandle pHandle, final Object atom, final HGAtomAttrib attrib)
{
LiveHandle lHandle = null;
if ( (attrib.getFlags() & HGSystemFlags.MANAGED) != 0)
lHandle = new LiveHandle(atom, pHandle, attrib.getFlags(), attrib.getRetrievalCount(), attrib.getLastAccessTime());
else
lHandle = new LiveHandle(atom, pHandle, attrib.getFlags());
insert(lHandle);
return lHandle;
}
public HGLiveHandle atomRefresh(HGLiveHandle handle, Object atom, boolean replace)
{
LiveHandle existing = liveHandles.get(handle.getPersistent());
if (existing != null)
{
atoms.remove(existing.getRef());
existing.setRef(atom);
atoms.put(atom, existing);
}
else
{
LiveHandle lHandle = (LiveHandle)handle;
lHandle.setRef(atom);
insert(lHandle);
}
return handle;
}
public void freeze(HGLiveHandle handle)
{
queueThread.addAction(new AtomDetachAction((LiveHandle)handle));
}
public void unfreeze(HGLiveHandle handle)
{
queueThread.addAction(new AddAtomAction((LiveHandle)handle));
}
public boolean isFrozen(HGLiveHandle handle)
{
LiveHandle h = (LiveHandle)handle;
return h.prev == null && h.next == null;
}
/**
* <p>Remove a live handle and all its associations from the cache.</p>
*/
public void remove(HGHandle handle)
{
HGLiveHandle lhdl = null;
if (handle instanceof HGLiveHandle)
lhdl = (HGLiveHandle)handle;
else
lhdl = get(handle.getPersistent());
if (lhdl != null)
{
incidenceSets.remove(lhdl.getPersistent());
atoms.remove(lhdl.getRef());
liveHandles.remove(lhdl.getPersistent());
queueThread.addAction(new AtomDetachAction((LiveHandle)lhdl));
((LiveHandle)lhdl).setRef(null);
}
}
//
// Actions for queue maintenance
//
private class AtomAccessedAction implements Runnable
{
LiveHandle atom;
AtomAccessedAction(LiveHandle atom) { this.atom = atom; }
public void run()
{
importanceUp(atom);
}
}
private class AddAtomAction implements Runnable
{
LiveHandle atom;
AddAtomAction(LiveHandle atom) { this.atom = atom; }
public void run()
{
if (atomQueueTail == null)
atomQueueTail = atom;
else
{
atom.next = atomQueueTail;
atomQueueTail.prev = atom;
atomQueueTail = atom;
importanceUp(atom);
}
}
}
private class AtomDetachAction implements Runnable
{
LiveHandle atom;
public AtomDetachAction(LiveHandle atom) { this.atom = atom; }
public void run()
{
if (atom.prev != null)
atom.prev.next = atom.next;
if (atom.next != null)
atom.next.prev = atom.prev;
atom.prev = atom.next = null;
}
}
private class AtomsEvictAction implements Runnable
{
long n;
AtomsEvictAction(long n) { this.n = n; }
public void run()
{
if (atomQueueTail == null)
return;
LiveHandle newTail = atomQueueTail;
while (n-- > 0 && newTail.next != null)
{
LiveHandle current = newTail;
liveHandles.remove(newTail.getPersistent());
atoms.remove(newTail.getRef());
hg.getEventManager().dispatch(hg, new HGAtomEvictEvent(newTail, newTail.getRef()));
newTail.setRef(null);
newTail = newTail.next;
current.prev = current.next = null;
}
if (newTail.next == null)
{
liveHandles.remove(newTail.getPersistent());
atoms.remove(newTail.getRef());
hg.getEventManager().dispatch(hg, new HGAtomEvictEvent(newTail, newTail.getRef()));
newTail.setRef(null);
newTail.prev = newTail.next = null;
atomQueueTail = null;
}
else
{
newTail.prev = null;
atomQueueTail = newTail;
}
}
}
}