/* * 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.lang.ref.ReferenceQueue; import java.util.HashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; 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.handle.WeakHandle; import org.hypergraphdb.handle.WeakManagedHandle; import org.hypergraphdb.transaction.HGTransactionConfig; import org.hypergraphdb.transaction.TxCacheMap; import org.hypergraphdb.transaction.VBox; import org.hypergraphdb.transaction.VBoxBody; import org.hypergraphdb.util.CloseMe; import org.hypergraphdb.util.WeakIdentityHashMap; /** * * <p> * This cache implementation interacts with the Java garbage collector, by using * the <code>java.lang.ref</code> facilities, in order to implement its eviction * policy. The eviction policy is the following: an atom is removed from the cache * if and only if either (1) its runtime Java instance is garbage collected or (2) * it is explicitly removed from the HyperGraph DB. Freezing an atom will keep it in * the cache even when it's garbage collected, but not if it's removed from DB altogether. * </p> * * <p> * For atom caching, two maps are maintained: handle->Java object and Java object->handle. * The latter is an <em>identity map</code>, using the <code>==</code> operator and * the <code>System.identityHashCode</code> instead of the regular Object <code>equals</code> * and <code>hashCode</code> methods. This is because if an atom changes (e.g. some of its * properties are modified), the results of <code>equals</code> and <code>hashCode</code> * will most likely change as well and it will become impossible to recover the atom's * handle. Unfortunately, a long standing bug in the JVM has <code>System.identityHashCode</code> * highly inefficient. * </p> * * @author Borislav Iordanov * */ public class WeakRefAtomCache implements HGAtomCache { private HyperGraph graph = null; private HGCache<HGPersistentHandle, IncidenceSet> incidenceCache = null; // to be configured by the HyperGraph instance private CacheMap<HGPersistentHandle, WeakHandle> liveHandles = null; private TxCacheMap<HGPersistentHandle, WeakHandle> liveHandlesTx = null; private CacheMap<Object, HGLiveHandle> atoms = null; private TxCacheMap<Object, HGLiveHandle> atomsTx = null; private CacheMap<HGLiveHandle, Object> frozenAtoms = null; private ColdAtoms coldAtoms = new ColdAtoms(); public static final long DEFAULT_PHANTOM_QUEUE_POLL_INTERVAL = 500; private ReadWriteLock gcLock = new ReentrantReadWriteLock(); private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); private PhantomCleanup cleanupThread = new PhantomCleanup(); private HGTransactionConfig cleanupTxConfig = new HGTransactionConfig(); private long phantomQueuePollInterval = DEFAULT_PHANTOM_QUEUE_POLL_INTERVAL; private boolean closing = false; // // This handle class is used to read atoms during closing of the cache. Because // closing the HyperGraph may involve a lot of cleanup activity where it's necessary // to read atoms (mostly types) into main memory just temporarily, we need some // way to enact this temporarily. // private static class TempLiveHandle extends DefaultManagedLiveHandle { public TempLiveHandle(Object ref, HGPersistentHandle persistentHandle, byte flags) { super(ref, persistentHandle, flags, 0, 0); } public void setRef(Object ref) { this.ref = ref; } } private static class ClearHandleAction implements Runnable { private WeakHandle h; public ClearHandleAction(WeakHandle h) { this.h = h;} public void run() { h.clear(); } } private void processRefQueue() throws InterruptedException { WeakHandle ref = (WeakHandle)refQueue.remove(phantomQueuePollInterval); while (ref != null) { liveHandles.drop(ref.getPersistent()); ref.clear(); synchronized (ref) { ref.notifyAll(); } ref = (WeakHandle)refQueue.poll(); } } private void processRefQueueTx() throws InterruptedException { // need WeakHandle.geRef to return when ref is enqueued - deadlock otherwise! WeakHandle.returnEnqueued.set(Boolean.TRUE); WeakHandle ref = (WeakHandle)refQueue.remove(phantomQueuePollInterval); while (ref != null) { final HGPersistentHandle h = ref.getPersistent(); gcLock.writeLock().lock(); // we won't allow modifications to the cache maps during this try { TxCacheMap<HGPersistentHandle, WeakHandle>.Box theBox = liveHandlesTx.boxOf(h); if (theBox == null) continue; boolean keep = false; for (VBoxBody<WeakHandle> body = theBox.getBody(); body != null && !keep; body = body.next) { // body.value can be null here if the atom got garbage collected before // a transaction committed if (body.value != null /* && body.value.getRef() != null */) //2nd test added AP 2012-01-31 { Object x = body.value.getRef(); if (x != null) { VBox<?> bb = atomsTx.boxOf(x); if (bb != null) keep = true; } } } if (!keep) liveHandles.drop(h); } finally { gcLock.writeLock().unlock(); ref.clear(); synchronized (ref) { ref.notifyAll(); } ref = (WeakHandle)refQueue.poll(); } } WeakHandle.returnEnqueued.set(Boolean.FALSE); } private class PhantomCleanup extends Thread { private volatile boolean done; public void run() { cleanupTxConfig.setNoStorage(true); for (done = false; !done; ) { try { if (graph.getConfig().isTransactional()) processRefQueueTx(); else processRefQueue(); } catch (InterruptedException exc) { Thread.currentThread().interrupt(); } catch (Throwable t) { System.err.println("PhantomCleanup thread caught an unexpected exception, stack trace follows:"); t.printStackTrace(System.err); } } } public void end() { this.done = true; } } public WeakRefAtomCache(HyperGraph graph) { this.graph = graph; if (graph.getConfig().isTransactional()) { atoms = atomsTx = new TxCacheMap<Object, HGLiveHandle>( graph.getTransactionManager(), WeakIdentityHashMap.class, null); atomsTx.setReturnLatestAvailable(true); liveHandles = liveHandlesTx = new TxCacheMap<HGPersistentHandle, WeakHandle>( graph.getTransactionManager(), HashMap.class, null); frozenAtoms = new TxCacheMap<HGLiveHandle, Object>(graph.getTransactionManager(), null, null); } else { atoms = new HashCacheMap<Object, HGLiveHandle>(); // need a weak hash cache map? liveHandles = new HashCacheMap<HGPersistentHandle, WeakHandle>(); frozenAtoms = new HashCacheMap<HGLiveHandle, Object>(); } cleanupThread.setPriority(Thread.MAX_PRIORITY); cleanupThread.setDaemon(true); cleanupThread.start(); } public void setIncidenceCache(HGCache<HGPersistentHandle, IncidenceSet> cache) { this.incidenceCache= cache; } public HGCache<HGPersistentHandle, IncidenceSet> getIncidenceCache() { return incidenceCache; } public void setHyperGraph(HyperGraph hg) { this.graph = hg; cleanupThread.setName("HGCACHE Cleanup - " + graph.getLocation()); } public HGLiveHandle atomAdded(HGPersistentHandle pHandle, Object atom, final HGAtomAttrib attrib) { if (closing) { HGLiveHandle result = new TempLiveHandle(atom, pHandle, attrib.getFlags()); atoms.put(atom, result); return result; } WeakHandle h = null; h = liveHandles.get(pHandle); if (h != null) return h; if (attrib != null && (attrib.getFlags() & HGSystemFlags.MANAGED) != 0) h = new WeakManagedHandle(atom, pHandle, attrib.getFlags(), refQueue, attrib.getRetrievalCount(), attrib.getLastAccessTime()); else h = new WeakHandle(atom, pHandle, attrib == null ? HGSystemFlags.DEFAULT : attrib.getFlags(), refQueue); graph.getTransactionManager().getContext().getCurrent().addAbortAction(new ClearHandleAction(h)); gcLock.readLock().lock(); try { atoms.put(atom, h); liveHandles.put(pHandle, h); coldAtoms.add(atom); return h; } finally { gcLock.readLock().unlock(); } } public HGLiveHandle atomRead(HGPersistentHandle pHandle, Object atom, final HGAtomAttrib attrib) { if (closing) { HGLiveHandle result = new TempLiveHandle(atom, pHandle, attrib.getFlags()); atoms.put(atom, result); return result; } WeakHandle h = null; h = liveHandles.get(pHandle); if (h != null) return h; if (attrib != null && (attrib.getFlags() & HGSystemFlags.MANAGED) != 0) h = new WeakManagedHandle(atom, pHandle, attrib.getFlags(), refQueue, attrib.getRetrievalCount(), attrib.getLastAccessTime()); else h = new WeakHandle(atom, pHandle, attrib == null ? HGSystemFlags.DEFAULT : attrib.getFlags(), refQueue); graph.getTransactionManager().getContext().getCurrent().addAbortAction(new ClearHandleAction(h)); // Important to updates the atoms map first to prevent garbage collection // of the liveHandles entry due to previously removed runtime instance of the // same atom. gcLock.readLock().lock(); try { atoms.load(atom, h); liveHandles.load(pHandle, h); coldAtoms.add(atom); return h; } finally { gcLock.readLock().unlock(); } } public HGLiveHandle atomRefresh(HGLiveHandle handle, Object atom, boolean replace) { if (handle.getRef() == atom) return handle; // same atom, nothing to do if (closing) { if (handle instanceof WeakHandle) ((WeakHandle)handle).clear(); else ((TempLiveHandle)handle).setRef(atom); return handle; } WeakHandle newLive = null; if (handle instanceof WeakManagedHandle) newLive = new WeakManagedHandle(atom, handle.getPersistent(), handle.getFlags(), refQueue, ((WeakManagedHandle)handle).getRetrievalCount(), ((WeakManagedHandle)handle).getRetrievalCount()); else newLive = new WeakHandle(atom, handle.getPersistent(), handle.getFlags(), refQueue); graph.getTransactionManager().getContext().getCurrent().addAbortAction(new ClearHandleAction(newLive)); // We are updating two maps here. Because the atoms map is weak-ref-based, the // correct order is to update it first for otherwise a previously kicked in // garbage collection of the corresponding 'liveHandles' entry will create a // race condition Object curr = handle.getRef(); ((WeakHandle)handle).clear(); // If we have some other Java instance as the atom, we need to force a replace // even if strictly speaking we don't need to (e.g. this happens with type wrappers) // because in case of a roll back the obligatory atoms.remove will be reversed. gcLock.readLock().lock(); try { if (replace || curr != null) { if (curr != null) atoms.remove(curr); atoms.put(atom, newLive); liveHandles.put(handle.getPersistent(), newLive); } else { atoms.load(atom, newLive); liveHandles.load(handle.getPersistent(), newLive); } coldAtoms.add(atom); return newLive; } finally { gcLock.readLock().unlock(); } } public void close() { closing = true; cleanupThread.end(); while (cleanupThread.isAlive() ) try { cleanupThread.join(); } catch (InterruptedException ex) { } frozenAtoms.clear(); incidenceCache.clear(); if (incidenceCache instanceof CloseMe) ((CloseMe)incidenceCache).close(); atoms.clear(); liveHandles.clear(); } public HGLiveHandle get(HGPersistentHandle pHandle) { WeakHandle h = liveHandles.get(pHandle); if (h != null) h.accessed(); return h; } public HGLiveHandle get(Object atom) { return atoms.get(atom); } public void remove(HGHandle handle) { HGLiveHandle lhdl = null; if (handle instanceof HGLiveHandle) lhdl = (HGLiveHandle)handle; else lhdl = get(handle.getPersistent()); if (lhdl != null) { atoms.remove(lhdl.getRef()); liveHandles.remove(lhdl.getPersistent()); } } public boolean isFrozen(HGLiveHandle handle) { return frozenAtoms.get(handle) != null; } public void freeze(HGLiveHandle handle) { Object atom = handle.getRef(); if (atom != null) { if (graph.getTransactionManager().getContext().getCurrent().isReadOnly()) frozenAtoms.load(handle, atom); else frozenAtoms.put(handle, atom); } } public void unfreeze(HGLiveHandle handle) { frozenAtoms.remove(handle); } // for debugging purposes public void printSizes() { System.out.println("atoms map: " + atomsTx.mapSize()); System.out.println("liveHandles map: " + liveHandlesTx.mapSize()); System.out.println("cold atoms: " + coldAtoms.size()); System.out.println("frozen atoms: " + frozenAtoms.size()); } }