/******************************************************************************* * Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.apache.jdbm; import javax.crypto.Cipher; import java.io.IOException; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; /** * A DB wrapping and caching another DB. * * @author Jan Kotek * @author Alex Boisvert * @author Cees de Groot * * TODO add 'cache miss' statistics */ public class DBCacheRef extends DBCache { private static final boolean debug = false; /** * If Soft Cache is enabled, this contains softly referenced clean entries. * If entry became dirty, it is moved to _hash with limited size. * This map is accessed from SoftCache Disposer thread, so all access must be * synchronized */ protected LongHashMap _softHash; /** * Reference queue used to collect Soft Cache entries */ protected ReferenceQueue<ReferenceCacheEntry> _refQueue; /** * Thread in which Soft Cache references are disposed */ protected Thread _softRefThread; protected static AtomicInteger threadCounter = new AtomicInteger(0); /** counter which counts number of insert since last 'action'*/ protected int insertCounter = 0; private final boolean _autoClearReferenceCacheOnLowMem; private final byte _cacheType; /** * Construct a CacheRecordManager wrapping another DB and * using a given cache policy. */ public DBCacheRef(String filename, boolean readonly, boolean transactionDisabled, Cipher cipherIn, Cipher cipherOut, boolean useRandomAccessFile, boolean deleteFilesAfterClose, byte cacheType, boolean cacheAutoClearOnLowMem, boolean lockingDisabled) { super(filename, readonly, transactionDisabled, cipherIn, cipherOut, useRandomAccessFile, deleteFilesAfterClose, lockingDisabled); this._cacheType = cacheType; _autoClearReferenceCacheOnLowMem = cacheAutoClearOnLowMem; _softHash = new LongHashMap<ReferenceCacheEntry>(); _refQueue = new ReferenceQueue<ReferenceCacheEntry>(); _softRefThread = new Thread( new SoftRunnable(this, _refQueue), "JDBM Soft Cache Disposer " + (threadCounter.incrementAndGet())); _softRefThread.setDaemon(true); _softRefThread.start(); } void clearCacheIfLowOnMem() { insertCounter = 0; if(!_autoClearReferenceCacheOnLowMem) return; Runtime r = Runtime.getRuntime(); long max = r.maxMemory(); if(max == Long.MAX_VALUE) return; double free = r.freeMemory(); double total = r.totalMemory(); //We believe that free refers to total not max. //Increasing heap size to max would increase to max free = free + (max-total); if(debug) System.err.println("DBCache: freemem = " +free + " = "+(free/max)+"%"); if(free<1e7 || free*4 <max) clearCache(); } public synchronized <A> A fetch(long recid, Serializer<A> serializer, boolean disableCache) throws IOException { if (disableCache) return super.fetch(recid, serializer, disableCache); else return fetch(recid, serializer); } public synchronized void delete(long recid) throws IOException { checkNotClosed(); super.delete(recid); synchronized (_hashDirties){ _hashDirties.remove(recid); } synchronized (_softHash) { Object e = _softHash.remove(recid); if (e != null && e instanceof ReferenceCacheEntry) { ((ReferenceCacheEntry)e).clear(); } } if(needsAutoCommit()) commit(); } public synchronized <A> void update(final long recid, A obj, Serializer<A> serializer) throws IOException { checkNotClosed(); synchronized (_softHash) { //soft cache can not contain dirty objects Object e = _softHash.remove(recid); if (e != null && e instanceof ReferenceCacheEntry) { ((ReferenceCacheEntry)e).clear(); } } synchronized (_hashDirties){ //put into dirty cache final DirtyCacheEntry e = new DirtyCacheEntry(); e._recid = recid; e._obj = obj; e._serializer = serializer; _hashDirties.put(recid,e); } if(needsAutoCommit()) commit(); } public synchronized <A> A fetch(long recid, Serializer<A> serializer) throws IOException { checkNotClosed(); synchronized (_softHash) { Object e = _softHash.get(recid); if (e != null) { if(e instanceof ReferenceCacheEntry) e = ((ReferenceCacheEntry)e).get(); if (e != null) { return (A) e; } } } synchronized (_hashDirties){ DirtyCacheEntry e2 = _hashDirties.get(recid); if(e2!=null){ return (A) e2._obj; } } A value = super.fetch(recid, serializer); if(needsAutoCommit()) commit(); synchronized (_softHash) { if (_cacheType == SOFT) _softHash.put(recid, new SoftCacheEntry(recid, value, _refQueue)); else if (_cacheType == WEAK) _softHash.put(recid, new WeakCacheEntry(recid, value, _refQueue)); else _softHash.put(recid,value); } return value; } public synchronized void close() { checkNotClosed(); updateCacheEntries(); super.close(); _softHash = null; _softRefThread.interrupt(); } public synchronized void rollback() { checkNotClosed(); // discard all cache entries since we don't know which entries // where part of the transaction synchronized (_softHash) { Iterator<ReferenceCacheEntry> iter = _softHash.valuesIterator(); while (iter.hasNext()) { ReferenceCacheEntry e = iter.next(); e.clear(); } _softHash.clear(); } super.rollback(); } protected boolean isCacheEntryDirty(DirtyCacheEntry entry) { return _hashDirties.get(entry._recid) != null; } protected void setCacheEntryDirty(DirtyCacheEntry entry, boolean dirty) { if (dirty) { _hashDirties.put(entry._recid, entry); } else { _hashDirties.remove(entry._recid); } } interface ReferenceCacheEntry { long getRecid(); void clear(); Object get(); } @SuppressWarnings("unchecked") static final class SoftCacheEntry extends SoftReference implements ReferenceCacheEntry { protected final long _recid; public long getRecid() { return _recid; } SoftCacheEntry(long recid, Object obj, ReferenceQueue queue) { super(obj, queue); _recid = recid; } } @SuppressWarnings("unchecked") static final class WeakCacheEntry extends WeakReference implements ReferenceCacheEntry { protected final long _recid; public long getRecid() { return _recid; } WeakCacheEntry(long recid, Object obj, ReferenceQueue queue) { super(obj, queue); _recid = recid; } } /** * Runs in separate thread and cleans SoftCache. * Runnable auto exists when CacheRecordManager is GCed * * @author Jan Kotek */ static final class SoftRunnable implements Runnable { private ReferenceQueue<ReferenceCacheEntry> entryQueue; private WeakReference<DBCacheRef> db2; public SoftRunnable(DBCacheRef db, ReferenceQueue<ReferenceCacheEntry> entryQueue) { this.db2 = new WeakReference<DBCacheRef>(db); this.entryQueue = entryQueue; } public void run() { while (true) try { //collect next item from cache, //limit 10000 ms is to keep periodically checking if db was GCed ReferenceCacheEntry e = (ReferenceCacheEntry) entryQueue.remove(10000); //check if db was GCed, cancel in that case DBCacheRef db = db2.get(); if (db == null) return; if (e != null) { synchronized (db._softHash) { int counter = 0; while (e != null) { db._softHash.remove(e.getRecid()); e = (SoftCacheEntry) entryQueue.poll(); if(debug) counter++; } if(debug) System.err.println("DBCache: "+counter+" objects released from ref cache."); } }else{ //check memory consumption every 10 seconds db.clearCacheIfLowOnMem(); } } catch (InterruptedException e) { return; } catch (Throwable e) { //this thread must keep spinning, //otherwise SoftCacheEntries would not be disposed e.printStackTrace(); } } } public void clearCache() { if(debug) System.err.println("DBCache: Clear cache"); synchronized (_softHash) { if(_cacheType!=HARD){ Iterator<ReferenceCacheEntry> iter = _softHash.valuesIterator(); while (iter.hasNext()) { ReferenceCacheEntry e = iter.next(); e.clear(); } } _softHash.clear(); } } }