/* * 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.IdentityHashMap; import java.util.Iterator; import java.util.Map; 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.event.HGAtomEvictEvent; import org.hypergraphdb.handle.DefaultManagedLiveHandle; import org.hypergraphdb.handle.HGLiveHandle; import org.hypergraphdb.handle.PhantomHandle; import org.hypergraphdb.handle.PhantomManagedHandle; import org.hypergraphdb.util.CloseMe; import org.hypergraphdb.util.WeakIdentityHashMap; public class PhantomRefAtomCache implements HGAtomCache { private HyperGraph graph = null; private HGCache<HGPersistentHandle, IncidenceSet> incidenceCache = null; // to be configured by the HyperGraph instance private final Map<HGPersistentHandle, PhantomHandle> liveHandles = new HashMap<HGPersistentHandle, PhantomHandle>(); private Map<Object, HGLiveHandle> atoms = new WeakIdentityHashMap<Object, HGLiveHandle>(); private Map<HGLiveHandle, Object> frozenAtoms = new IdentityHashMap<HGLiveHandle, Object>(); private ColdAtoms coldAtoms = new ColdAtoms(); public static final long DEFAULT_PHANTOM_QUEUE_POLL_INTERVAL = 500; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @SuppressWarnings("unchecked") private ReferenceQueue refQueue = new ReferenceQueue(); private PhantomCleanup phantomCleanupThread = new PhantomCleanup(); 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 temporarility. // 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 void processRefQueue() throws InterruptedException { PhantomHandle ref = (PhantomHandle)refQueue.remove(phantomQueuePollInterval); while (ref != null) { graph.getEventManager().dispatch(graph, new HGAtomEvictEvent(ref, ref.fetchRef())); lock.writeLock().lock(); try { liveHandles.remove(ref.getPersistent()); } finally { lock.writeLock().unlock(); } ref.clear(); synchronized (ref) { ref.notifyAll(); } ref = (PhantomHandle)refQueue.poll(); } } private class PhantomCleanup extends Thread { private boolean done; public void run() { PhantomHandle.returnEnqueued.set(Boolean.TRUE); for (done = false; !done; ) { try { 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); } } PhantomHandle.returnEnqueued.set(Boolean.FALSE); } public void end() { this.done = true; } } public PhantomRefAtomCache() { phantomCleanupThread.setPriority(Thread.MAX_PRIORITY); phantomCleanupThread.setDaemon(true); phantomCleanupThread.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; phantomCleanupThread.setName("HGCACHE Cleanup - " + graph.getLocation()); } public HGLiveHandle atomAdded(HGPersistentHandle pHandle, Object atom, final HGAtomAttrib attrib) { return atomRead(pHandle, atom, attrib); } @SuppressWarnings("unchecked") 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; } lock.writeLock().lock(); PhantomHandle h = null; try { h = liveHandles.get(pHandle); if (h != null) return h; if ( (attrib.getFlags() & HGSystemFlags.MANAGED) == 0) h = new PhantomHandle(atom, pHandle, attrib.getFlags(), refQueue); else h = new PhantomManagedHandle(atom, pHandle, attrib.getFlags(), refQueue, attrib.getRetrievalCount(), attrib.getLastAccessTime()); atoms.put(atom, h); liveHandles.put(pHandle, h); coldAtoms.add(atom); } finally { lock.writeLock().unlock(); } return h; } public HGLiveHandle atomRefresh(HGLiveHandle handle, Object atom, boolean replace) { if (closing) { if (handle instanceof PhantomHandle) ((PhantomHandle)handle).storeRef(atom); else ((TempLiveHandle)handle).setRef(atom); return handle; } if (handle == null) throw new NullPointerException("atomRefresh: handle is null."); lock.writeLock().lock(); try { PhantomHandle ph = (PhantomHandle)handle; PhantomHandle existing = liveHandles.get(ph.getPersistent()); if (existing != ph) { if (existing != null) { liveHandles.remove(existing.getPersistent()); atoms.remove(existing.getRef()); } ph.storeRef(atom); liveHandles.put(ph.getPersistent(), ph); atoms.put(atom, ph); coldAtoms.add(atom); } else if (ph.getRef() != atom) { atoms.remove(ph.getRef()); ph.storeRef(atom); atoms.put(atom, ph); } } finally { lock.writeLock().unlock(); } return handle; } public void close() { closing = true; phantomCleanupThread.end(); while (phantomCleanupThread.isAlive() ) try { phantomCleanupThread.join(); } catch (InterruptedException ex) { } PhantomHandle.returnEnqueued.set(Boolean.TRUE); try { processRefQueue(); } catch (InterruptedException ex) { } for (Iterator<Map.Entry<HGPersistentHandle, PhantomHandle>> i = liveHandles.entrySet().iterator(); i.hasNext(); ) { PhantomHandle h = i.next().getValue(); Object x = h.fetchRef(); graph.getEventManager().dispatch(graph, new HGAtomEvictEvent(h, x)); if (h.isEnqueued()) { h.clear(); } } PhantomHandle.returnEnqueued.set(Boolean.FALSE); frozenAtoms.clear(); incidenceCache.clear(); if (incidenceCache instanceof CloseMe) ((CloseMe)incidenceCache).close(); atoms.clear(); liveHandles.clear(); } public HGLiveHandle get(HGPersistentHandle pHandle) { lock.readLock().lock(); try { PhantomHandle h = liveHandles.get(pHandle); if (h != null) h.accessed(); return h; } finally { lock.readLock().unlock(); } } public HGLiveHandle get(Object atom) { lock.readLock().lock(); try { return atoms.get(atom); } finally { lock.readLock().unlock(); } } public void remove(HGHandle handle) { lock.writeLock().lock(); try { HGLiveHandle lhdl = null; if (handle instanceof HGLiveHandle) lhdl = (HGLiveHandle)handle; else lhdl = get(handle.getPersistent()); if (lhdl != null) { atoms.remove(lhdl.getRef()); // Shouldn't use clear here, since we might be gc-ing the ref! if (lhdl instanceof PhantomHandle) ((PhantomHandle)lhdl).storeRef(null); liveHandles.remove(lhdl.getPersistent()); } } finally { lock.writeLock().unlock(); } } public boolean isFrozen(HGLiveHandle handle) { synchronized (frozenAtoms) { return frozenAtoms.containsKey(handle); } } public void freeze(HGLiveHandle handle) { Object atom = handle.getRef(); if (atom != null) synchronized (frozenAtoms) { frozenAtoms.put(handle, atom); } } public void unfreeze(HGLiveHandle handle) { synchronized (frozenAtoms) { frozenAtoms.remove(handle); } } }