Index: lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java =================================================================== --- lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java (revision 1177798) +++ lucene/contrib/misc/src/java/org/apache/lucene/search/SearcherManager.java (working copy) @@ -152,7 +152,15 @@ } } } - swapSearcher(newSearcher); + boolean success = false; + try { + swapSearcher(newSearcher); + success = true; + } finally { + if (!success) { + release(newSearcher); + } + } return true; } else { return false; @@ -204,7 +212,12 @@ * affected, and they should still call {@link #release} * after they are done. */ @Override - public void close() throws IOException { - swapSearcher(null); + public synchronized void close() throws IOException { + if (currentSearcher != null) { + // make sure we can call this more than once + // closeable javadoc says: + // if this is already closed then invoking this method has no effect. + swapSearcher(null); + } } } Index: lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java =================================================================== --- lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java (revision 1177798) +++ lucene/contrib/misc/src/test/org/apache/lucene/search/TestSearcherManager.java (working copy) @@ -18,10 +18,17 @@ */ import java.io.IOException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.index.ThreadedIndexingAndSearchingTestCase; +import org.apache.lucene.store.AlreadyClosedException; +import org.apache.lucene.store.Directory; import org.apache.lucene.util._TestUtil; public class TestSearcherManager extends ThreadedIndexingAndSearchingTestCase { @@ -110,4 +117,74 @@ } mgr.close(); } + + public void testIntermediateClose() throws IOException, InterruptedException { + Directory dir = newDirectory(); + IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig( + TEST_VERSION_CURRENT, new MockAnalyzer(random))); + writer.addDocument(new Document()); + writer.commit(); + final CountDownLatch awaitEnterWarm = new CountDownLatch(1); + final CountDownLatch awaitClose = new CountDownLatch(1); + + final SearcherManager searcherManager = new SearcherManager(dir, + new SearcherWarmer() { + @Override + public void warm(IndexSearcher s) throws IOException { + try { + awaitEnterWarm.countDown(); + awaitClose.await(); + } catch (InterruptedException e) { + // + } + } + }); + IndexSearcher searcher = searcherManager.acquire(); + try { + assertEquals(1, searcher.getIndexReader().numDocs()); + } finally { + searcherManager.release(searcher); + } + writer.addDocument(new Document()); + writer.commit(); + final AtomicBoolean success = new AtomicBoolean(false); + final AtomicBoolean triedReopen = new AtomicBoolean(false); + final Throwable[] exc = new Throwable[1]; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + triedReopen.set(true); + searcherManager.maybeReopen(); + success.set(true); + } catch (AlreadyClosedException e) { + // expected + } catch (Throwable e) { + exc[0] = e; + // use success as the barrier here to make sure we see the write + success.set(false); + + } + } + }); + thread.start(); + awaitEnterWarm.await(); + for (int i = 0; i < 2; i++) { + searcherManager.close(); + } + awaitClose.countDown(); + thread.join(); + try { + searcherManager.acquire(); + fail("already closed"); + } catch (AlreadyClosedException ex) { + // expected + } + assertFalse(success.get()); + assertTrue(triedReopen.get()); + assertNull("" + exc[0], exc[0]); + writer.close(); + dir.close(); + + } }