Index: lucene/src/test/org/apache/lucene/index/TestIndexReader.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestIndexReader.java (revision 1056428) +++ lucene/src/test/org/apache/lucene/index/TestIndexReader.java (working copy) @@ -1796,4 +1796,39 @@ assertTrue(IndexReader.indexExists(dir)); dir.close(); } + + // LUCENE-2474 + public void testReaderFinishedListener() throws Exception { + Directory dir = newDirectory(); + IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new WhitespaceAnalyzer(TEST_VERSION_CURRENT))); + ((LogMergePolicy) writer.getMergePolicy()).setMergeFactor(3); + writer.setInfoStream(VERBOSE ? System.out : null); + writer.addDocument(new Document()); + writer.commit(); + writer.addDocument(new Document()); + writer.commit(); + final IndexReader reader = writer.getReader(); + final int[] closeCount = new int[1]; + final IndexReader.ReaderFinishedListener listener = new IndexReader.ReaderFinishedListener() { + public void finished(IndexReader reader) { + closeCount[0]++; + } + }; + + IndexReader.addReaderFinishedListener(listener); + try { + + reader.close(); + + // Just the top reader + assertEquals(1, closeCount[0]); + writer.close(); + + // Now also the subs + assertEquals(3, closeCount[0]); + dir.close(); + } finally { + IndexReader.removeReaderFinishedListener(listener); + } + } } Index: lucene/src/java/org/apache/lucene/index/ParallelReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/ParallelReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/ParallelReader.java (working copy) @@ -21,7 +21,6 @@ import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.FieldSelectorResult; import org.apache.lucene.document.Fieldable; -import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close import java.io.IOException; import java.util.*; @@ -486,8 +485,6 @@ readers.get(i).close(); } } - - FieldCache.DEFAULT.purge(this); } @Override Index: lucene/src/java/org/apache/lucene/index/SegmentReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/SegmentReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/SegmentReader.java (working copy) @@ -38,7 +38,6 @@ import org.apache.lucene.store.IndexOutput; import org.apache.lucene.util.BitVector; import org.apache.lucene.util.CloseableThreadLocal; -import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close /** * @lucene.experimental @@ -242,13 +241,9 @@ storeCFSReader.close(); } - // Force FieldCache to evict our entries at this - // point. If the exception occurred while - // initializing the core readers, then - // origInstance will be null, and we don't want - // to call FieldCache.purge (it leads to NPE): + // Now, notify any on close listeners: if (origInstance != null) { - FieldCache.DEFAULT.purge(origInstance); + IndexReader.notifyReaderFinishedListeners(origInstance); } } } @@ -1368,4 +1363,10 @@ public int getTermInfosIndexDivisor() { return core.termsIndexDivisor; } + + @Override + protected void readerFinished(IndexReader reader) { + // Do nothing here -- we have more careful control on + // when to notify that a SegmentReader has finished + } } Index: lucene/src/java/org/apache/lucene/index/MultiReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/MultiReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/MultiReader.java (working copy) @@ -29,7 +29,6 @@ import org.apache.lucene.index.DirectoryReader.MultiTermEnum; import org.apache.lucene.index.DirectoryReader.MultiTermPositions; import org.apache.lucene.search.Similarity; -import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close /** An IndexReader which reads multiple indexes, appending * their content. */ @@ -416,11 +415,6 @@ subReaders[i].close(); } } - - // NOTE: only needed in case someone had asked for - // FieldCache for top-level reader (which is generally - // not a good idea): - FieldCache.DEFAULT.purge(this); } @Override Index: lucene/src/java/org/apache/lucene/index/DirectoryReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/DirectoryReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/DirectoryReader.java (working copy) @@ -36,7 +36,6 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.Lock; import org.apache.lucene.store.LockObtainFailedException; -import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close /** * An IndexReader which reads indexes with multiple segments. @@ -854,11 +853,6 @@ } } - // NOTE: only needed in case someone had asked for - // FieldCache for top-level reader (which is generally - // not a good idea): - FieldCache.DEFAULT.purge(this); - if (writer != null) { // Since we just closed, writer may now be able to // delete unused files: Index: lucene/src/java/org/apache/lucene/index/FilterIndexReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/FilterIndexReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/FilterIndexReader.java (working copy) @@ -20,7 +20,6 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.store.Directory; -import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close import java.io.IOException; import java.util.Collection; @@ -244,11 +243,6 @@ @Override protected void doClose() throws IOException { in.close(); - - // NOTE: only needed in case someone had asked for - // FieldCache for top-level reader (which is generally - // not a good idea): - FieldCache.DEFAULT.purge(this); } Index: lucene/src/java/org/apache/lucene/index/IndexReader.java =================================================================== --- lucene/src/java/org/apache/lucene/index/IndexReader.java (revision 1056428) +++ lucene/src/java/org/apache/lucene/index/IndexReader.java (working copy) @@ -17,20 +17,22 @@ * limitations under the License. */ -import org.apache.lucene.document.Document; -import org.apache.lucene.document.FieldSelector; -import org.apache.lucene.search.Similarity; -import org.apache.lucene.store.*; -import org.apache.lucene.util.ArrayUtil; - +import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.Closeable; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.FieldSelector; +import org.apache.lucene.search.Similarity; +import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close +import org.apache.lucene.store.*; +import org.apache.lucene.util.ArrayUtil; + /** IndexReader is an abstract class, providing an interface for accessing an index. Search of an index is done entirely through this abstract interface, so that any subclass which implements it is searchable. @@ -74,7 +76,7 @@ IndexReader instance; use your own (non-Lucene) objects instead. */ -public abstract class IndexReader implements Cloneable,Closeable { +public abstract class IndexReader implements Cloneable, Closeable { /** * Constants describing field properties, for example used for @@ -119,6 +121,58 @@ private final AtomicInteger refCount = new AtomicInteger(); + /** + * A custom listener that's invoked when the IndexReader + * is finished. + * + *

For a SegmentReader, this listener is called only + * once all SegmentReaders sharing the same core, are + * closed. At this point it is safe for apps to evict + * this reader from any caches keyed on {@link + * #getCoreCacheKey}. This is the same interface that + * {@link FieldCache} uses, internally, to evict entries.

+ * + *

For other readers, this listener is called when they + * are closed.

+ * + * @lucene.experimental + */ + public static interface ReaderFinishedListener { + public void finished(IndexReader reader); + } + + private static final Collection readerFinishedListeners = new HashSet(); + + /** Expert: adds a {@link ReaderFinishedListener}. + * + * @lucene.experimental */ + public synchronized static void addReaderFinishedListener(ReaderFinishedListener listener) { + readerFinishedListeners.add(listener); + } + + /** Expert: remove a previously added {@link ReaderFinishedListener}. + * + * @lucene.experimental */ + public synchronized static void removeReaderFinishedListener(ReaderFinishedListener listener) { + readerFinishedListeners.remove(listener); + } + + synchronized static void notifyReaderFinishedListeners(IndexReader reader) { + for(ReaderFinishedListener listener : readerFinishedListeners) { + listener.finished(reader); + } + } + + static { + // So field cache purges entries when a reader is finished + addReaderFinishedListener(new ReaderFinishedListener() { + // @Override + public void finished(IndexReader reader) { + FieldCache.DEFAULT.purge(reader); + } + }); + } + static int DEFAULT_TERMS_INDEX_DIVISOR = 1; /** Expert: returns the current refCount for this reader */ @@ -190,8 +244,13 @@ refCount.incrementAndGet(); } } + readerFinished(this); } } + + protected void readerFinished(IndexReader reader) { + notifyReaderFinishedListeners(reader); + } protected IndexReader() { refCount.set(1);