Index: lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java
===================================================================
--- lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java (revision 1176585)
+++ lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java (working copy)
@@ -20,7 +20,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Semaphore;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
@@ -67,7 +67,7 @@
// Current searcher
private volatile IndexSearcher currentSearcher;
private final SearcherWarmer warmer;
- private final AtomicBoolean reopening = new AtomicBoolean();
+ private final Semaphore reopening = new Semaphore(1);
private final ExecutorService es;
/** Opens an initial searcher from the Directory.
@@ -136,7 +136,7 @@
// Ensure only 1 thread does reopen at once; other
// threads just return immediately:
- if (!reopening.getAndSet(true)) {
+ if (reopening.tryAcquire()) {
try {
IndexReader newReader = currentSearcher.getIndexReader().reopen();
if (newReader != currentSearcher.getIndexReader()) {
@@ -158,7 +158,7 @@
return false;
}
} finally {
- reopening.set(false);
+ reopening.release();
}
} else {
return false;
@@ -168,12 +168,14 @@
/** Obtain the current IndexSearcher. You must match
* every call to get with one call to {@link #release};
* it's best to do so in a finally clause. */
- public synchronized IndexSearcher get() {
- if (currentSearcher == null) {
- throw new AlreadyClosedException("this SearcherManager is closed");
- }
- currentSearcher.getIndexReader().incRef();
- return currentSearcher;
+ public IndexSearcher acquire() {
+ IndexSearcher searcher;
+ do {
+ if ((searcher = currentSearcher) == null) {
+ throw new AlreadyClosedException("this SearcherManager is closed");
+ }
+ } while (!searcher.getIndexReader().tryIncRef());
+ return searcher;
}
/** Release the searcher previously obtained with {@link
@@ -186,7 +188,7 @@
searcher.getIndexReader().decRef();
}
- // Replaces old searcher with new one
+ // Replaces old searcher with new one - needs to be synced to make close() work
private synchronized void swapSearcher(IndexSearcher newSearcher)
throws IOException {
IndexSearcher oldSearcher = currentSearcher;
Index: lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java
===================================================================
--- lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java (revision 1176585)
+++ lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java (working copy)
@@ -36,7 +36,7 @@
protected IndexSearcher getFinalSearcher() throws Exception {
writer.commit();
mgr.maybeReopen();
- return mgr.get();
+ return mgr.acquire();
}
private SearcherManager mgr;
@@ -94,7 +94,7 @@
mgr.maybeReopen();
}
- return mgr.get();
+ return mgr.acquire();
}
@Override
Index: lucene/src/java/org/apache/lucene/index/IndexReader.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/IndexReader.java (revision 1176585)
+++ lucene/src/java/org/apache/lucene/index/IndexReader.java (working copy)
@@ -200,11 +200,45 @@
* references.
*
* @see #decRef
+ * @see #tryIncRef
*/
public void incRef() {
ensureOpen();
refCount.incrementAndGet();
}
+
+ /**
+ * Expert: increments the refCount of this IndexReader
+ * instance only if the IndexReader has not been closed yet
+ * and returns true iff the refCount was
+ * successfully incremented, otherwise false.
+ * If this method returns false the reader is either
+ * already closed or is currently been closed. Either way this
+ * reader instance shouldn't be used by an application unless
+ * true is returned.
+ *
+ * RefCounts are used to determine when a + * reader can be closed safely, i.e. as soon as there are + * no more references. Be sure to always call a + * corresponding {@link #decRef}, in a finally clause; + * otherwise the reader may never be closed. Note that + * {@link #close} simply calls decRef(), which means that + * the IndexReader will not really be closed until {@link + * #decRef} has been called for all outstanding + * references. + * + * @see #decRef + * @see #incRef + */ + public boolean tryIncRef() { + int count; + while ((count = refCount.get()) > 0) { + if(refCount.compareAndSet(count, count+1)) { + return true; + } + } + return false; + } /** {@inheritDoc} */ @Override Index: lucene/src/test/org/apache/lucene/index/TestIndexReader.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestIndexReader.java (revision 1176585) +++ lucene/src/test/org/apache/lucene/index/TestIndexReader.java (working copy) @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.HashMap; +import java.util.Random; import java.util.Set; import java.util.SortedSet; import org.junit.Assume; @@ -1403,4 +1404,71 @@ r.close(); dir.close(); } + + public void testTryIncRef() throws CorruptIndexException, LockObtainFailedException, IOException { + Directory dir = newDirectory(); + IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))); + writer.addDocument(new Document()); + writer.commit(); + IndexReader r = IndexReader.open(dir); + assertTrue(r.tryIncRef()); + r.decRef(); + r.close(); + assertFalse(r.tryIncRef()); + writer.close(); + dir.close(); + } + + public void testStressTryIncRef() throws CorruptIndexException, LockObtainFailedException, IOException, InterruptedException { + Directory dir = newDirectory(); + IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))); + writer.addDocument(new Document()); + writer.commit(); + IndexReader r = IndexReader.open(dir); + int numThreads = atLeast(2); + + IncThread[] threads = new IncThread[numThreads]; + System.out.println(numThreads); + for (int i = 0; i < threads.length; i++) { + threads[i] = new IncThread(r, random); + threads[i].start(); + } + Thread.sleep(100); + + assertTrue(r.tryIncRef()); + r.decRef(); + r.close(); + + for (int i = 0; i < threads.length; i++) { + threads[i].join(); + assertNull(threads[i].failed); + } + assertFalse(r.tryIncRef()); + writer.close(); + dir.close(); + } + + static class IncThread extends Thread { + final IndexReader toInc; + final Random random; + Throwable failed; + + IncThread(IndexReader toInc, Random random) { + this.toInc = toInc; + this.random = random; + } + + @Override + public void run() { + try { + while (toInc.tryIncRef()) { + assertFalse(toInc.hasDeletions()); + toInc.decRef(); + } + assertFalse(toInc.tryIncRef()); + } catch (Throwable e) { + failed = e; + } + } + } }