package proj.zoie.impl.indexing; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.log4j.Logger; import org.apache.lucene.index.IndexReader; import proj.zoie.api.IndexReaderFactory; import proj.zoie.api.ZoieException; import proj.zoie.api.ZoieMultiReader; public class DefaultReaderCache<R extends IndexReader> extends AbstractReaderCache<R> { private static final Logger log = Logger.getLogger(DefaultReaderCache.class); private final Thread _maintenance; private volatile boolean alreadyShutdown = false; private volatile List<ZoieMultiReader<R>> cachedreaders = new ArrayList<ZoieMultiReader<R>>(0); private volatile long cachedreaderTimestamp = 0; private final ReentrantReadWriteLock cachedreadersLock = new ReentrantReadWriteLock(); private volatile ConcurrentLinkedQueue<List<ZoieMultiReader<R>>> returningIndexReaderQueue = new ConcurrentLinkedQueue<List<ZoieMultiReader<R>>>(); private final ReentrantReadWriteLock returningIndexReaderQueueLock = new ReentrantReadWriteLock(); private final Object cachemonitor = new Object(); private long _freshness = 10000L; private final WeakReference<IndexReaderFactory<R>> _readerfactory; public DefaultReaderCache(IndexReaderFactory<R> readerfactory) { _readerfactory = new WeakReference<IndexReaderFactory<R>>(readerfactory); _maintenance = newMaintenanceThread(); _maintenance.setDaemon(true); } @Override public List<ZoieMultiReader<R>> getIndexReaders() { cachedreadersLock.readLock().lock(); List<ZoieMultiReader<R>> readers = cachedreaders; for (ZoieMultiReader<R> r : readers) { r.incZoieRef(); } cachedreadersLock.readLock().unlock(); return readers; } @Override public void returnIndexReaders(List<ZoieMultiReader<R>> readers) { if (readers == null || readers.size() == 0) return; returningIndexReaderQueueLock.readLock().lock(); try { returningIndexReaderQueue.add(readers); } finally { returningIndexReaderQueueLock.readLock().unlock(); } } @Override public void refreshCache(long timeout) throws ZoieException { long begintime = System.currentTimeMillis(); while (cachedreaderTimestamp <= begintime) { synchronized (cachemonitor) { cachemonitor.notifyAll(); long elapsed = System.currentTimeMillis() - begintime; if (elapsed > timeout) { log.debug("refreshCached reader timeout in " + elapsed + "ms"); throw new ZoieException("refreshCached reader timeout in " + elapsed + "ms"); } long timetowait = Math.min(timeout - elapsed, 200); try { cachemonitor.wait(timetowait); } catch (InterruptedException e) { log.warn("refreshCache", e); } } } } @Override public void shutdown() { _freshness = 30000L; alreadyShutdown = true; } @Override public void start() { _maintenance.start(); } @Override public long getFreshness() { return _freshness; } @Override public void setFreshness(long freshness) { _freshness = freshness; } private Thread newMaintenanceThread() { return new MaintenanceThread(); } private class MaintenanceThread extends Thread { public MaintenanceThread() { super("DefaultReaderCache-zoie-indexReader-maintenance"); } @Override public void run() { while (true) { try { synchronized (cachemonitor) { cachemonitor.wait(_freshness); } } catch (InterruptedException e) { Thread.interrupted(); // clear interrupted state } List<ZoieMultiReader<R>> newreaders = null; if (alreadyShutdown) { newreaders = new ArrayList<ZoieMultiReader<R>>(); // clean up and quit } else { try { IndexReaderFactory<R> readerfactory = _readerfactory.get(); if (readerfactory != null) { newreaders = readerfactory.getIndexReaders(); } else { newreaders = new ArrayList<ZoieMultiReader<R>>(); } } catch (IOException e) { log.info("DefaultReaderCache-zoie-indexReader-maintenance", e); newreaders = new ArrayList<ZoieMultiReader<R>>(); } } List<ZoieMultiReader<R>> oldreaders = cachedreaders; cachedreadersLock.writeLock().lock(); cachedreaders = newreaders; cachedreadersLock.writeLock().unlock(); cachedreaderTimestamp = System.currentTimeMillis(); synchronized (cachemonitor) { cachemonitor.notifyAll(); } // return the old cached reader if (!oldreaders.isEmpty()) returnIndexReaders(oldreaders); // process the returing index reader queue returningIndexReaderQueueLock.writeLock().lock(); ConcurrentLinkedQueue<List<ZoieMultiReader<R>>> oldreturningIndexReaderQueue = returningIndexReaderQueue; returningIndexReaderQueue = new ConcurrentLinkedQueue<List<ZoieMultiReader<R>>>(); returningIndexReaderQueueLock.writeLock().unlock(); for (List<ZoieMultiReader<R>> readers : oldreturningIndexReaderQueue) { for (ZoieMultiReader<R> r : readers) { r.decZoieRef(); } } if (_readerfactory.get() == null && cachedreaders.size() == 0) { log.info("ZoieSystem has been GCed. Exiting DefaultReaderCache Maintenance Thread " + this); break; // if the system is GCed, we quit. } } } } public static ReaderCacheFactory FACTORY = new ReaderCacheFactory() { @Override public <R extends IndexReader> AbstractReaderCache<R> newInstance( IndexReaderFactory<R> readerfactory) { return new DefaultReaderCache<R>(readerfactory); } }; }