Index: lucene/contrib/misc/src/test/org/apache/lucene/index/TestNRTManager.java =================================================================== --- lucene/contrib/misc/src/test/org/apache/lucene/index/TestNRTManager.java (revision 1198594) +++ lucene/contrib/misc/src/test/org/apache/lucene/index/TestNRTManager.java (working copy) @@ -19,15 +19,22 @@ import java.io.IOException; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.TextField; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.SearcherManager; import org.apache.lucene.search.SearcherWarmer; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; +import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.NRTCachingDirectory; import org.apache.lucene.util.LuceneTestCase.UseNoMemoryExpensiveCodec; +import org.apache.lucene.util.ThreadInterruptedException; @UseNoMemoryExpensiveCodec public class TestNRTManager extends ThreadedIndexingAndSearchingTestCase { @@ -244,4 +251,75 @@ nrtThread.close(); nrt.close(); } + + /* + * LUCENE-3528 - NRTManager hangs in certain situations + */ + public void testThreadStarvationNoDeleteNRTReader() throws IOException { + IndexWriterConfig conf = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)); + Directory d = newDirectory(); + final CountDownLatch latch = new CountDownLatch(1); + LatchedIndexWriter writer = new LatchedIndexWriter(d, conf, latch); + final NRTManager manager = new NRTManager(writer, null, null); + NRTManagerReopenThread thread = new NRTManagerReopenThread(manager, 0.01, 0.01); + thread.start(); + Document doc = new Document(); + doc.add(newField("test","test", TextField.TYPE_STORED)); + long gen = manager.addDocument(doc); + final SearcherManager searcherManager = manager.waitForGeneration(gen, false); + assertFalse(gen < manager.getCurrentSearchingGen(false)); + Thread t = new Thread() { + public void run() { + try { + IndexSearcher oldSearcher = searcherManager.acquire(); + IndexSearcher acquire = manager.getSearcherManager(false).acquire(); + while (oldSearcher == acquire) { // wait until reopen thread did the reopen + acquire.getIndexReader().decRef(); + acquire = manager.getSearcherManager(false).acquire(); + } + oldSearcher.getIndexReader().decRef(); + manager.maybeReopen(false); // kick off another reopen so we inc. the internal gen + } catch (IOException e) { + e.printStackTrace(); + } + latch.countDown(); // let the add below finish + } + }; + t.start(); + writer.waitAfterUpdate = true; // wait in addDocument to let some reopens go through + gen = manager.addDocument(doc); // once this returns the doc is already reflected in the last reopen + assertTrue(manager.getSearcherManager(false).isSearcherCurrent()); // yeah we are current already + /* + * if this fails we will wait forever if we call + * NRTManager#waitForGeneration(gen, false); since the IS is already current + * and its SearchManager generation will never be incremented. + */ + assertTrue("and we deadlock - no changes pending", gen <= manager.getCurrentSearchingGen(false)); + + } + + public static class LatchedIndexWriter extends IndexWriter { + + private CountDownLatch latch; + boolean waitAfterUpdate = false; + + public LatchedIndexWriter(Directory d, IndexWriterConfig conf, CountDownLatch latch) + throws CorruptIndexException, LockObtainFailedException, IOException { + super(d, conf); + this.latch = latch; + + } + + public void updateDocument(Term term, Iterable doc, Analyzer analyzer) + throws CorruptIndexException, IOException { + super.updateDocument(term, doc, analyzer); + try { + if (waitAfterUpdate) { + latch.await(); + } + } catch (InterruptedException e) { + throw new ThreadInterruptedException(e); + } + } + } }