mirror of https://github.com/apache/lucene.git
LUCENE-2474: add expert ReaderFinishedListener API, for external caches to evict entries for readers
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1063498 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
fcf6e305b4
commit
323ea4134d
|
@ -812,6 +812,11 @@ New features
|
||||||
* LUCENE-2864: Add getMaxTermFrequency (maximum within-document TF) to
|
* LUCENE-2864: Add getMaxTermFrequency (maximum within-document TF) to
|
||||||
FieldInvertState so that it can be used in Similarity.computeNorm.
|
FieldInvertState so that it can be used in Similarity.computeNorm.
|
||||||
(Robert Muir)
|
(Robert Muir)
|
||||||
|
|
||||||
|
* LUCENE-2474: Added expert ReaderFinishedListener API to
|
||||||
|
IndexReader, to allow apps that maintain external per-segment caches
|
||||||
|
to evict entries when a segment is finished. (Shay Banon, Yonik
|
||||||
|
Seeley, Mike McCandless)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.lucene.store.instantiated;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -52,6 +53,7 @@ public class InstantiatedIndexReader extends IndexReader {
|
||||||
public InstantiatedIndexReader(InstantiatedIndex index) {
|
public InstantiatedIndexReader(InstantiatedIndex index) {
|
||||||
super();
|
super();
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -758,6 +759,7 @@ public class MemoryIndex implements Serializable {
|
||||||
|
|
||||||
private MemoryIndexReader() {
|
private MemoryIndexReader() {
|
||||||
super(); // avoid as much superclass baggage as possible
|
super(); // avoid as much superclass baggage as possible
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Info getInfo(String fieldName) {
|
private Info getInfo(String fieldName) {
|
||||||
|
|
|
@ -37,8 +37,6 @@ import org.apache.lucene.index.codecs.CodecProvider;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
||||||
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.
|
* An IndexReader which reads indexes with multiple segments.
|
||||||
*/
|
*/
|
||||||
|
@ -106,6 +104,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
} else {
|
} else {
|
||||||
this.codecs = codecs;
|
this.codecs = codecs;
|
||||||
}
|
}
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
|
|
||||||
// To reduce the chance of hitting FileNotFound
|
// To reduce the chance of hitting FileNotFound
|
||||||
// (and having to retry), we open segments in
|
// (and having to retry), we open segments in
|
||||||
|
@ -117,6 +116,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
|
readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
|
||||||
|
readers[i].readerFinishedListeners = readerFinishedListeners;
|
||||||
success = true;
|
success = true;
|
||||||
} finally {
|
} finally {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -146,6 +146,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
} else {
|
} else {
|
||||||
this.codecs = codecs;
|
this.codecs = codecs;
|
||||||
}
|
}
|
||||||
|
readerFinishedListeners = writer.getReaderFinishedListeners();
|
||||||
|
|
||||||
// IndexWriter synchronizes externally before calling
|
// IndexWriter synchronizes externally before calling
|
||||||
// us, which ensures infos will not change; so there's
|
// us, which ensures infos will not change; so there's
|
||||||
|
@ -160,6 +161,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
final SegmentInfo info = infos.info(i);
|
final SegmentInfo info = infos.info(i);
|
||||||
assert info.dir == dir;
|
assert info.dir == dir;
|
||||||
readers[i] = writer.readerPool.getReadOnlyClone(info, true, termInfosIndexDivisor);
|
readers[i] = writer.readerPool.getReadOnlyClone(info, true, termInfosIndexDivisor);
|
||||||
|
readers[i].readerFinishedListeners = readerFinishedListeners;
|
||||||
success = true;
|
success = true;
|
||||||
} finally {
|
} finally {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -182,11 +184,14 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
|
|
||||||
/** This constructor is only used for {@link #reopen()} */
|
/** This constructor is only used for {@link #reopen()} */
|
||||||
DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
|
DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
|
||||||
boolean readOnly, boolean doClone, int termInfosIndexDivisor, CodecProvider codecs) throws IOException {
|
boolean readOnly, boolean doClone, int termInfosIndexDivisor, CodecProvider codecs,
|
||||||
|
Collection<ReaderFinishedListener> readerFinishedListeners) throws IOException {
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
this.segmentInfos = infos;
|
this.segmentInfos = infos;
|
||||||
this.termInfosIndexDivisor = termInfosIndexDivisor;
|
this.termInfosIndexDivisor = termInfosIndexDivisor;
|
||||||
|
this.readerFinishedListeners = readerFinishedListeners;
|
||||||
|
|
||||||
if (codecs == null) {
|
if (codecs == null) {
|
||||||
this.codecs = CodecProvider.getDefault();
|
this.codecs = CodecProvider.getDefault();
|
||||||
} else {
|
} else {
|
||||||
|
@ -232,8 +237,10 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
|
|
||||||
// this is a new reader; in case we hit an exception we can close it safely
|
// this is a new reader; in case we hit an exception we can close it safely
|
||||||
newReader = SegmentReader.get(readOnly, infos.info(i), termInfosIndexDivisor);
|
newReader = SegmentReader.get(readOnly, infos.info(i), termInfosIndexDivisor);
|
||||||
|
newReader.readerFinishedListeners = readerFinishedListeners;
|
||||||
} else {
|
} else {
|
||||||
newReader = newReaders[i].reopenSegment(infos.info(i), doClone, readOnly);
|
newReader = newReaders[i].reopenSegment(infos.info(i), doClone, readOnly);
|
||||||
|
assert newReader.readerFinishedListeners == readerFinishedListeners;
|
||||||
}
|
}
|
||||||
if (newReader == newReaders[i]) {
|
if (newReader == newReaders[i]) {
|
||||||
// this reader will be shared between the old and the new one,
|
// this reader will be shared between the old and the new one,
|
||||||
|
@ -357,6 +364,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
writeLock = null;
|
writeLock = null;
|
||||||
hasChanges = false;
|
hasChanges = false;
|
||||||
}
|
}
|
||||||
|
assert newReader.readerFinishedListeners != null;
|
||||||
|
|
||||||
return newReader;
|
return newReader;
|
||||||
}
|
}
|
||||||
|
@ -391,7 +399,9 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
// TODO: right now we *always* make a new reader; in
|
// TODO: right now we *always* make a new reader; in
|
||||||
// the future we could have write make some effort to
|
// the future we could have write make some effort to
|
||||||
// detect that no changes have occurred
|
// detect that no changes have occurred
|
||||||
return writer.getReader();
|
IndexReader reader = writer.getReader();
|
||||||
|
reader.readerFinishedListeners = readerFinishedListeners;
|
||||||
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexReader doReopen(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
|
private IndexReader doReopen(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
|
||||||
|
@ -458,7 +468,7 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
|
|
||||||
private synchronized DirectoryReader doReopen(SegmentInfos infos, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
|
private synchronized DirectoryReader doReopen(SegmentInfos infos, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
|
||||||
DirectoryReader reader;
|
DirectoryReader reader;
|
||||||
reader = new DirectoryReader(directory, infos, subReaders, starts, openReadOnly, doClone, termInfosIndexDivisor, codecs);
|
reader = new DirectoryReader(directory, infos, subReaders, starts, openReadOnly, doClone, termInfosIndexDivisor, codecs, readerFinishedListeners);
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -808,11 +818,6 @@ class DirectoryReader extends IndexReader implements Cloneable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
if (writer != null) {
|
||||||
// Since we just closed, writer may now be able to
|
// Since we just closed, writer may now be able to
|
||||||
// delete unused files:
|
// delete unused files:
|
||||||
|
|
|
@ -22,13 +22,14 @@ import org.apache.lucene.document.FieldSelector;
|
||||||
import org.apache.lucene.index.IndexReader.ReaderContext;
|
import org.apache.lucene.index.IndexReader.ReaderContext;
|
||||||
import org.apache.lucene.store.Directory;
|
import org.apache.lucene.store.Directory;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close
|
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
/** A <code>FilterIndexReader</code> contains another IndexReader, which it
|
/** A <code>FilterIndexReader</code> contains another IndexReader, which it
|
||||||
* uses as its basic source of data, possibly transforming the data along the
|
* uses as its basic source of data, possibly transforming the data along the
|
||||||
|
@ -286,6 +287,7 @@ public class FilterIndexReader extends IndexReader {
|
||||||
public FilterIndexReader(IndexReader in) {
|
public FilterIndexReader(IndexReader in) {
|
||||||
super();
|
super();
|
||||||
this.in = in;
|
this.in = in;
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -391,11 +393,6 @@ public class FilterIndexReader extends IndexReader {
|
||||||
@Override
|
@Override
|
||||||
protected void doClose() throws IOException {
|
protected void doClose() throws IOException {
|
||||||
in.close();
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -454,4 +451,16 @@ public class FilterIndexReader extends IndexReader {
|
||||||
buffer.append(')');
|
buffer.append(')');
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void addReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.addReaderFinishedListener(listener);
|
||||||
|
in.addReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.removeReaderFinishedListener(listener);
|
||||||
|
in.removeReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import java.io.IOException;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -81,6 +82,65 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
*/
|
*/
|
||||||
public abstract class IndexReader implements Cloneable,Closeable {
|
public abstract class IndexReader implements Cloneable,Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom listener that's invoked when the IndexReader
|
||||||
|
* is finished.
|
||||||
|
*
|
||||||
|
* <p>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.</p>
|
||||||
|
*
|
||||||
|
* <p>For other readers, this listener is called when they
|
||||||
|
* are closed.</p>
|
||||||
|
*
|
||||||
|
* @lucene.experimental
|
||||||
|
*/
|
||||||
|
public static interface ReaderFinishedListener {
|
||||||
|
public void finished(IndexReader reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Impls must set this if they may call add/removeReaderFinishedListener:
|
||||||
|
protected volatile Collection<ReaderFinishedListener> readerFinishedListeners;
|
||||||
|
|
||||||
|
/** Expert: adds a {@link ReaderFinishedListener}. The
|
||||||
|
* provided listener is also added to any sub-readers, if
|
||||||
|
* this is a composite reader. Also, any reader reopened
|
||||||
|
* or cloned from this one will also copy the listeners at
|
||||||
|
* the time of reopen.
|
||||||
|
*
|
||||||
|
* @lucene.experimental */
|
||||||
|
public void addReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
readerFinishedListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Expert: remove a previously added {@link ReaderFinishedListener}.
|
||||||
|
*
|
||||||
|
* @lucene.experimental */
|
||||||
|
public void removeReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
readerFinishedListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyReaderFinishedListeners() {
|
||||||
|
// Defensive (should never be null -- all impls must set
|
||||||
|
// this):
|
||||||
|
if (readerFinishedListeners != null) {
|
||||||
|
|
||||||
|
// Clone the set so that we don't have to sync on
|
||||||
|
// readerFinishedListeners while invoking them:
|
||||||
|
for(ReaderFinishedListener listener : new HashSet<ReaderFinishedListener>(readerFinishedListeners)) {
|
||||||
|
listener.finished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void readerFinished() {
|
||||||
|
notifyReaderFinishedListeners();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants describing field properties, for example used for
|
* Constants describing field properties, for example used for
|
||||||
* {@link IndexReader#getFieldNames(FieldOption)}.
|
* {@link IndexReader#getFieldNames(FieldOption)}.
|
||||||
|
@ -195,6 +255,7 @@ public abstract class IndexReader implements Cloneable,Closeable {
|
||||||
refCount.incrementAndGet();
|
refCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
readerFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.apache.lucene.analysis.Analyzer;
|
import org.apache.lucene.analysis.Analyzer;
|
||||||
|
@ -365,6 +366,13 @@ public class IndexWriter implements Closeable {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for all SegmentReaders we open
|
||||||
|
private final Collection<IndexReader.ReaderFinishedListener> readerFinishedListeners = Collections.synchronizedSet(new HashSet<IndexReader.ReaderFinishedListener>());
|
||||||
|
|
||||||
|
Collection<IndexReader.ReaderFinishedListener> getReaderFinishedListeners() throws IOException {
|
||||||
|
return readerFinishedListeners;
|
||||||
|
}
|
||||||
|
|
||||||
/** Holds shared SegmentReader instances. IndexWriter uses
|
/** Holds shared SegmentReader instances. IndexWriter uses
|
||||||
* SegmentReaders for 1) applying deletes, 2) doing
|
* SegmentReaders for 1) applying deletes, 2) doing
|
||||||
* merges, 3) handing out a real-time reader. This pool
|
* merges, 3) handing out a real-time reader. This pool
|
||||||
|
@ -574,6 +582,7 @@ public class IndexWriter implements Closeable {
|
||||||
// synchronized
|
// synchronized
|
||||||
// Returns a ref, which we xfer to readerMap:
|
// Returns a ref, which we xfer to readerMap:
|
||||||
sr = SegmentReader.get(false, info.dir, info, readBufferSize, doOpenStores, termsIndexDivisor);
|
sr = SegmentReader.get(false, info.dir, info, readBufferSize, doOpenStores, termsIndexDivisor);
|
||||||
|
sr.readerFinishedListeners = readerFinishedListeners;
|
||||||
|
|
||||||
if (info.dir == directory) {
|
if (info.dir == directory) {
|
||||||
// Only pool if reader is not external
|
// Only pool if reader is not external
|
||||||
|
|
|
@ -20,10 +20,11 @@ package org.apache.lucene.index;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.apache.lucene.document.Document;
|
import org.apache.lucene.document.Document;
|
||||||
import org.apache.lucene.document.FieldSelector;
|
import org.apache.lucene.document.FieldSelector;
|
||||||
import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close
|
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
import org.apache.lucene.util.ReaderUtil;
|
import org.apache.lucene.util.ReaderUtil;
|
||||||
|
@ -82,6 +83,7 @@ public class MultiReader extends IndexReader implements Cloneable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
starts[subReaders.length] = maxDoc;
|
starts[subReaders.length] = maxDoc;
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
return ReaderUtil.buildReaderContext(this);
|
return ReaderUtil.buildReaderContext(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,11 +347,6 @@ public class MultiReader extends IndexReader implements Cloneable {
|
||||||
subReaders[i].close();
|
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
|
@Override
|
||||||
|
@ -389,4 +386,20 @@ public class MultiReader extends IndexReader implements Cloneable {
|
||||||
public ReaderContext getTopReaderContext() {
|
public ReaderContext getTopReaderContext() {
|
||||||
return topLevelContext;
|
return topLevelContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.addReaderFinishedListener(listener);
|
||||||
|
for(IndexReader sub : subReaders) {
|
||||||
|
sub.addReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.removeReaderFinishedListener(listener);
|
||||||
|
for(IndexReader sub : subReaders) {
|
||||||
|
sub.removeReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.apache.lucene.document.FieldSelector;
|
||||||
import org.apache.lucene.document.FieldSelectorResult;
|
import org.apache.lucene.document.FieldSelectorResult;
|
||||||
import org.apache.lucene.document.Fieldable;
|
import org.apache.lucene.document.Fieldable;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close
|
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -73,6 +72,7 @@ public class ParallelReader extends IndexReader {
|
||||||
public ParallelReader(boolean closeSubReaders) throws IOException {
|
public ParallelReader(boolean closeSubReaders) throws IOException {
|
||||||
super();
|
super();
|
||||||
this.incRefReaders = !closeSubReaders;
|
this.incRefReaders = !closeSubReaders;
|
||||||
|
readerFinishedListeners = Collections.synchronizedSet(new HashSet<ReaderFinishedListener>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
@ -529,8 +529,6 @@ public class ParallelReader extends IndexReader {
|
||||||
readers.get(i).close();
|
readers.get(i).close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldCache.DEFAULT.purge(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -548,6 +546,21 @@ public class ParallelReader extends IndexReader {
|
||||||
return topLevelReaderContext;
|
return topLevelReaderContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.addReaderFinishedListener(listener);
|
||||||
|
for (IndexReader reader : readers) {
|
||||||
|
reader.addReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeReaderFinishedListener(ReaderFinishedListener listener) {
|
||||||
|
super.removeReaderFinishedListener(listener);
|
||||||
|
for (IndexReader reader : readers) {
|
||||||
|
reader.removeReaderFinishedListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.apache.lucene.util.BitVector;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.util.CloseableThreadLocal;
|
import org.apache.lucene.util.CloseableThreadLocal;
|
||||||
import org.apache.lucene.index.codecs.FieldsProducer;
|
import org.apache.lucene.index.codecs.FieldsProducer;
|
||||||
import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close
|
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,13 +182,9 @@ public class SegmentReader extends IndexReader implements Cloneable {
|
||||||
storeCFSReader.close();
|
storeCFSReader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force FieldCache to evict our entries at this
|
// Now, notify any ReaderFinished listeners:
|
||||||
// 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):
|
|
||||||
if (origInstance != null) {
|
if (origInstance != null) {
|
||||||
FieldCache.DEFAULT.purge(origInstance);
|
origInstance.notifyReaderFinishedListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -633,6 +628,7 @@ public class SegmentReader extends IndexReader implements Cloneable {
|
||||||
clone.si = si;
|
clone.si = si;
|
||||||
clone.readBufferSize = readBufferSize;
|
clone.readBufferSize = readBufferSize;
|
||||||
clone.pendingDeleteCount = pendingDeleteCount;
|
clone.pendingDeleteCount = pendingDeleteCount;
|
||||||
|
clone.readerFinishedListeners = readerFinishedListeners;
|
||||||
|
|
||||||
if (!openReadOnly && hasChanges) {
|
if (!openReadOnly && hasChanges) {
|
||||||
// My pending changes transfer to the new reader
|
// My pending changes transfer to the new reader
|
||||||
|
@ -1203,4 +1199,14 @@ public class SegmentReader extends IndexReader implements Cloneable {
|
||||||
public int getTermInfosIndexDivisor() {
|
public int getTermInfosIndexDivisor() {
|
||||||
return core.termsIndexDivisor;
|
return core.termsIndexDivisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readerFinished() {
|
||||||
|
// Do nothing here -- we have more careful control on
|
||||||
|
// when to notify that a SegmentReader has finished,
|
||||||
|
// because a given core is shared across many cloned
|
||||||
|
// SegmentReaders. We only notify once that core is no
|
||||||
|
// longer used (all SegmentReaders sharing it have been
|
||||||
|
// closed).
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,13 @@ public class FieldCacheImpl implements FieldCache { // Made Public so that
|
||||||
public Object getValue() { return value; }
|
public Object getValue() { return value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final static IndexReader.ReaderFinishedListener purgeReader = new IndexReader.ReaderFinishedListener() {
|
||||||
|
// @Override -- not until Java 1.6
|
||||||
|
public void finished(IndexReader reader) {
|
||||||
|
FieldCache.DEFAULT.purge(reader);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** Expert: Internal cache. */
|
/** Expert: Internal cache. */
|
||||||
final static class Cache<T> {
|
final static class Cache<T> {
|
||||||
Cache() {
|
Cache() {
|
||||||
|
@ -171,8 +178,10 @@ public class FieldCacheImpl implements FieldCache { // Made Public so that
|
||||||
synchronized (readerCache) {
|
synchronized (readerCache) {
|
||||||
innerCache = readerCache.get(readerKey);
|
innerCache = readerCache.get(readerKey);
|
||||||
if (innerCache == null) {
|
if (innerCache == null) {
|
||||||
|
// First time this reader is using FieldCache
|
||||||
innerCache = new HashMap<Entry<T>,Object>();
|
innerCache = new HashMap<Entry<T>,Object>();
|
||||||
readerCache.put(readerKey, innerCache);
|
readerCache.put(readerKey, innerCache);
|
||||||
|
reader.addReaderFinishedListener(purgeReader);
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
} else {
|
||||||
value = innerCache.get(key);
|
value = innerCache.get(key);
|
||||||
|
|
|
@ -1905,4 +1905,42 @@ public class TestIndexReader extends LuceneTestCase
|
||||||
dir.close();
|
dir.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LUCENE-2474
|
||||||
|
public void testReaderFinishedListener() throws Exception {
|
||||||
|
Directory dir = newDirectory();
|
||||||
|
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer()));
|
||||||
|
((LogMergePolicy) writer.getConfig().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]++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.addReaderFinishedListener(listener);
|
||||||
|
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
// Just the top reader
|
||||||
|
assertEquals(1, closeCount[0]);
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
// Now also the subs
|
||||||
|
assertEquals(3, closeCount[0]);
|
||||||
|
|
||||||
|
IndexReader reader2 = IndexReader.open(dir);
|
||||||
|
reader2.addReaderFinishedListener(listener);
|
||||||
|
|
||||||
|
closeCount[0] = 0;
|
||||||
|
reader2.close();
|
||||||
|
assertEquals(3, closeCount[0]);
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue