Index: src/test/org/apache/lucene/store/_TestResourceGCDirectory.java =================================================================== --- src/test/org/apache/lucene/store/_TestResourceGCDirectory.java (revision 0) +++ src/test/org/apache/lucene/store/_TestResourceGCDirectory.java (revision 0) @@ -0,0 +1,68 @@ +package org.apache.lucene.store; + +import java.io.IOException; + +public class _TestResourceGCDirectory extends Directory { + private RAMDirectory ramDirectory; + + public _TestResourceGCDirectory() { + ramDirectory = new RAMDirectory(); + } + + public String[] list() throws IOException { + return ramDirectory.list(); + } + + public boolean fileExists(String name) throws IOException { + return ramDirectory.fileExists(name); + } + + public long fileModified(String name) throws IOException { + return ramDirectory.fileModified(name); + } + + public void touchFile(String name) throws IOException { + ramDirectory.touchFile(name); + } + + public void deleteFile(String name) throws IOException { + ramDirectory.deleteFile(name); + } + + public void renameFile(String from, String to) throws IOException { + ramDirectory.renameFile(from, to); + } + + public long fileLength(String name) throws IOException { + return ramDirectory.fileLength(name); + } + + public IndexOutput createOutput(String name) throws IOException { + return ramDirectory.createOutput(name); + } + + public IndexInput openInput(String name) throws IOException { + RAMFile file = (RAMFile)ramDirectory.files.get(name); + return new _TestResourceGCIndexInput(file); + } + + public Lock makeLock(String name) { + return ramDirectory.makeLock(name); + } + + public void close() throws IOException { + ramDirectory.close(); + } + + public void setLockFactory(LockFactory lockFactory) { + ramDirectory.setLockFactory(lockFactory); + } + + public LockFactory getLockFactory() { + return ramDirectory.getLockFactory(); + } + + public String getLockID() { + return ramDirectory.getLockID(); + } +} Index: src/test/org/apache/lucene/store/_TestResourceGCIndexInput.java =================================================================== --- src/test/org/apache/lucene/store/_TestResourceGCIndexInput.java (revision 0) +++ src/test/org/apache/lucene/store/_TestResourceGCIndexInput.java (revision 0) @@ -0,0 +1,32 @@ +package org.apache.lucene.store; + +import java.io.IOException; + +public class _TestResourceGCIndexInput extends RAMInputStream { + class Counter { + int count; + } + + private Counter refCounter; + + public _TestResourceGCIndexInput(RAMFile file) throws IOException { + super(file); + refCounter = new Counter(); + refCounter.count = 1; + } + + public int getReferenceCount() { + return refCounter.count; + } + + public void close() { + super.close(); + refCounter.count--; + } + + public Object clone() { + _TestResourceGCIndexInput clone = (_TestResourceGCIndexInput)super.clone(); + refCounter.count++; + return clone; + } +} Index: src/test/org/apache/lucene/index/TestScorerResourceGC.java =================================================================== --- src/test/org/apache/lucene/index/TestScorerResourceGC.java (revision 0) +++ src/test/org/apache/lucene/index/TestScorerResourceGC.java (revision 0) @@ -0,0 +1,84 @@ +package org.apache.lucene.index; + +import java.io.IOException; + +import org.apache.lucene.analysis.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Hits; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.store._TestResourceGCDirectory; +import org.apache.lucene.store._TestResourceGCIndexInput; + +import junit.framework.TestCase; + +public class TestScorerResourceGC extends TestCase { + protected _TestResourceGCDirectory directory; + private static final String FIELD = "field"; + + protected String[] values = new String[] { "all", "fetch", "dogs dogs", + "like", "playing", "fetch", "all" }; + + protected IndexSearcher indexSearcher; + protected SegmentReader indexReader; + + protected void setUp() throws IOException { + directory = new _TestResourceGCDirectory(); + + IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), + true); + writer.setUseCompoundFile(false); + + for (int i = 0; i < values.length - 1; i++) { + Document doc = new Document(); + + doc.add(new Field(FIELD, values[i], Field.Store.YES, + Field.Index.TOKENIZED)); + doc.add(new Field(FIELD, values[i + 1], Field.Store.YES, + Field.Index.TOKENIZED)); + + writer.addDocument(doc); + } + + writer.optimize(); + writer.close(); + + indexSearcher = new IndexSearcher(directory); + indexReader = (SegmentReader)indexSearcher.getIndexReader(); + } + + protected void tearDown() { + } + + public void testResourceGC() throws IOException { + int initRefCount = ((_TestResourceGCIndexInput)indexReader.freqStream) + .getReferenceCount(); + + for (int v = 0; v < values.length - 1; v++) { + BooleanQuery query = new BooleanQuery(); + TermQuery term1 = new TermQuery(new Term(FIELD, values[v])); + query.add(term1, BooleanClause.Occur.MUST); + TermQuery term2 = new TermQuery(new Term(FIELD, values[v + 1])); + query.add(term2, BooleanClause.Occur.MUST); + + Hits hits = indexSearcher.search(query); + + final int HITS_PER_PAGE = 1; + for (int start = 0; start < hits.length(); start += HITS_PER_PAGE) { + int end = Math.min(hits.length(), start + HITS_PER_PAGE); + for (int i = start; i < end; i++) { + Document doc = hits.doc(i); + String value = doc.get(FIELD); + } + } + + int currRefCount = ((_TestResourceGCIndexInput)indexReader.freqStream) + .getReferenceCount(); + assertEquals(initRefCount, currRefCount); + } + } +} Index: src/java/org/apache/lucene/search/ConstantScoreQuery.java =================================================================== --- src/java/org/apache/lucene/search/ConstantScoreQuery.java (revision 464822) +++ src/java/org/apache/lucene/search/ConstantScoreQuery.java (working copy) @@ -132,6 +132,9 @@ public Explanation explain(int doc) throws IOException { throw new UnsupportedOperationException(); } + + protected void doClose() throws IOException { + } } Index: src/java/org/apache/lucene/search/BooleanScorer2.java =================================================================== --- src/java/org/apache/lucene/search/BooleanScorer2.java (revision 464822) +++ src/java/org/apache/lucene/search/BooleanScorer2.java (working copy) @@ -148,6 +148,11 @@ public Explanation explain(int docNr) throws IOException { return scorer.explain(docNr); } + protected void doClose() throws IOException { + if (scorer != null) { + scorer.close(); + } + } } private Scorer countingDisjunctionSumScorer(List scorers, @@ -355,5 +360,20 @@ return countingSumScorer.explain(doc); // misses coord factor. */ } + + protected void doClose() throws IOException { + for (int i = 0; i < requiredScorers.size(); i++) { + ((Scorer)requiredScorers.get(i)).close(); + } + for (int i = 0; i < optionalScorers.size(); i++) { + ((Scorer)optionalScorers.get(i)).close(); + } + for (int i = 0; i < prohibitedScorers.size(); i++) { + ((Scorer)prohibitedScorers.get(i)).close(); + } + if (countingSumScorer != null) { + countingSumScorer.close(); + } + } } Index: src/java/org/apache/lucene/search/MatchAllDocsQuery.java =================================================================== --- src/java/org/apache/lucene/search/MatchAllDocsQuery.java (revision 464822) +++ src/java/org/apache/lucene/search/MatchAllDocsQuery.java (working copy) @@ -25,6 +25,7 @@ import org.apache.lucene.search.Weight; import org.apache.lucene.util.ToStringUtils; +import java.io.IOException; import java.util.Set; /** @@ -79,6 +80,8 @@ return next(); } + protected void doClose() throws IOException { + } } private class MatchAllDocsWeight implements Weight { Index: src/java/org/apache/lucene/search/spans/SpanScorer.java =================================================================== --- src/java/org/apache/lucene/search/spans/SpanScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/spans/SpanScorer.java (working copy) @@ -101,4 +101,6 @@ return tfExplanation; } + protected void doClose() throws IOException { + } } Index: src/java/org/apache/lucene/search/ConjunctionScorer.java =================================================================== --- src/java/org/apache/lucene/search/ConjunctionScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/ConjunctionScorer.java (working copy) @@ -124,4 +124,10 @@ throw new UnsupportedOperationException(); } + protected void doClose() throws IOException { + Iterator i = scorers.iterator(); + while (i.hasNext()) { + ((Scorer)i.next()).close(); + } + } } Index: src/java/org/apache/lucene/search/DisjunctionMaxScorer.java =================================================================== --- src/java/org/apache/lucene/search/DisjunctionMaxScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/DisjunctionMaxScorer.java (working copy) @@ -186,4 +186,9 @@ } } + protected void doClose() throws IOException { + for (int i = 0; i < subScorers.size(); i++) { + ((Scorer)subScorers.get(i)).close(); + } + } } Index: src/java/org/apache/lucene/search/FilteredQuery.java =================================================================== --- src/java/org/apache/lucene/search/FilteredQuery.java (revision 464822) +++ src/java/org/apache/lucene/search/FilteredQuery.java (working copy) @@ -131,6 +131,12 @@ exp.setDescription ("removed by filter: "+exp.getDescription()); return exp; } + + protected void doClose() throws IOException { + if (scorer != null) { + scorer.close(); + } + } }; } }; Index: src/java/org/apache/lucene/search/PhraseScorer.java =================================================================== --- src/java/org/apache/lucene/search/PhraseScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/PhraseScorer.java (working copy) @@ -151,4 +151,9 @@ public String toString() { return "scorer(" + weight + ")"; } + protected void doClose() throws IOException { + for (PhrasePositions pp = first; pp != null; pp = pp.next) { + pp.tp.close(); + } + } } Index: src/java/org/apache/lucene/search/ReqOptSumScorer.java =================================================================== --- src/java/org/apache/lucene/search/ReqOptSumScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/ReqOptSumScorer.java (working copy) @@ -93,5 +93,14 @@ res.addDetail(optScorer.explain(doc)); return res; } + + protected void doClose() throws IOException { + if (reqScorer != null) { + reqScorer.close(); + } + if (optScorer != null) { + optScorer.close(); + } + } } Index: src/java/org/apache/lucene/search/DisjunctionSumScorer.java =================================================================== --- src/java/org/apache/lucene/search/DisjunctionSumScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/DisjunctionSumScorer.java (working copy) @@ -236,4 +236,10 @@ } return res; } + + protected void doClose() throws IOException { + for (int i = 0; i < subScorers.size(); i++) { + ((Scorer)subScorers.get(i)).close(); + } + } } Index: src/java/org/apache/lucene/search/BooleanScorer.java =================================================================== --- src/java/org/apache/lucene/search/BooleanScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/BooleanScorer.java (working copy) @@ -262,5 +262,9 @@ return buffer.toString(); } - + protected void doClose() throws IOException { + for (SubScorer sub = scorers; sub != null; sub = sub.next) { + sub.scorer.close(); + } + } } Index: src/java/org/apache/lucene/search/Scorer.java =================================================================== --- src/java/org/apache/lucene/search/Scorer.java (revision 464822) +++ src/java/org/apache/lucene/search/Scorer.java (working copy) @@ -107,4 +107,18 @@ */ public abstract Explanation explain(int doc) throws IOException; + private boolean isClosed = false; + + /** + * Reclaim resources associated with this scorer, such as term docs in + * TermScorer. + */ + public void close() throws IOException { + if (!isClosed) { + doClose(); + isClosed = true; + } + } + + protected abstract void doClose() throws IOException; } Index: src/java/org/apache/lucene/search/ReqExclScorer.java =================================================================== --- src/java/org/apache/lucene/search/ReqExclScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/ReqExclScorer.java (working copy) @@ -141,4 +141,13 @@ } return res; } + + protected void doClose() throws IOException { + if (reqScorer != null) { + reqScorer.close(); + } + if (exclScorer != null) { + exclScorer.close(); + } + } } Index: src/java/org/apache/lucene/search/NonMatchingScorer.java =================================================================== --- src/java/org/apache/lucene/search/NonMatchingScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/NonMatchingScorer.java (working copy) @@ -35,6 +35,9 @@ e.setDescription("No document matches."); return e; } + + protected void doClose() throws IOException { + } } Index: src/java/org/apache/lucene/search/IndexSearcher.java =================================================================== --- src/java/org/apache/lucene/search/IndexSearcher.java (revision 464822) +++ src/java/org/apache/lucene/search/IndexSearcher.java (working copy) @@ -130,6 +130,7 @@ if (scorer == null) return; scorer.score(collector); + scorer.close(); } public Query rewrite(Query original) throws IOException { Index: src/java/org/apache/lucene/search/TermScorer.java =================================================================== --- src/java/org/apache/lucene/search/TermScorer.java (revision 464822) +++ src/java/org/apache/lucene/search/TermScorer.java (working copy) @@ -148,6 +148,7 @@ docs[pointer] = doc = termDocs.doc(); freqs[pointer] = termDocs.freq(); } else { + termDocs.close(); doc = Integer.MAX_VALUE; } return result; @@ -185,4 +186,10 @@ /** Returns a string representation of this TermScorer. */ public String toString() { return "scorer(" + weight + ")"; } + + protected void doClose() throws IOException { + if (doc < Integer.MAX_VALUE) { + termDocs.close(); + } + } }