package org.apache.lucene.search; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.NRTManager; // javadocs import org.apache.lucene.search.IndexSearcher; // javadocs import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.Directory; /** * Utility class to safely share {@link IndexSearcher} instances across multiple * threads, while periodically reopening. This class ensures each searcher is * closed only once all threads have finished using it. * * <p> * Use {@link #acquire} to obtain the current searcher, and {@link #release} to * release it, like this: * * <pre class="prettyprint"> * IndexSearcher s = manager.acquire(); * try { * // Do searching, doc retrieval, etc. with s * } finally { * manager.release(s); * } * // Do not use s after this! * s = null; * </pre> * * <p> * In addition you should periodically call {@link #maybeReopen}. While it's * possible to call this just before running each query, this is discouraged * since it penalizes the unlucky queries that do the reopen. It's better to use * a separate background thread, that periodically calls maybeReopen. Finally, * be sure to call {@link #close} once you are done. * * <p> * <b>NOTE</b>: if you have an {@link IndexWriter}, it's better to use * {@link NRTManager} since that class pulls near-real-time readers from the * IndexWriter. * * @lucene.experimental */ public final class SearcherManager { private volatile IndexSearcher currentSearcher; private final ExecutorService es; private final SearcherWarmer warmer; private final Semaphore reopenLock = new Semaphore(1); /** * Creates and returns a new SearcherManager from the given {@link IndexWriter}. * @param writer the IndexWriter to open the IndexReader from. * @param applyAllDeletes If <code>true</code>, all buffered deletes will * be applied (made visible) in the {@link IndexSearcher} / {@link IndexReader}. * If <code>false</code>, the deletes may or may not be applied, but remain buffered * (in IndexWriter) so that they will be applied in the future. * Applying deletes can be costly, so if your app can tolerate deleted documents * being returned you might gain some performance by passing <code>false</code>. * See {@link IndexReader#openIfChanged(IndexReader, IndexWriter, boolean)}. * @param warmer An optional {@link SearcherWarmer}. Pass * <code>null</code> if you don't require the searcher to warmed * before going live. If this is <code>non-null</code> then a * merged segment warmer is installed on the * provided IndexWriter's config. * @param es An optional {@link ExecutorService} so different segments can * be searched concurrently (see {@link * IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}. Pass <code>null</code> * to search segments sequentially. * * @throws IOException */ public SearcherManager(IndexWriter writer, boolean applyAllDeletes, final SearcherWarmer warmer, final ExecutorService es) throws IOException { this.es = es; this.warmer = warmer; currentSearcher = new IndexSearcher(IndexReader.open(writer, applyAllDeletes)); if (warmer != null) { writer.getConfig().setMergedSegmentWarmer( new IndexWriter.IndexReaderWarmer() { @Override public void warm(IndexReader reader) throws IOException { warmer.warm(new IndexSearcher(reader, es)); } }); } } /** * Creates and returns a new SearcherManager from the given {@link Directory}. * @param dir the directory to open the IndexReader on. * @param warmer An optional {@link SearcherWarmer}. Pass * <code>null</code> if you don't require the searcher to warmed * before going live. If this is <code>non-null</code> then a * merged segment warmer is installed on the * provided IndexWriter's config. * @param es And optional {@link ExecutorService} so different segments can * be searched concurrently (see {@link * IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}. Pass <code>null</code> * to search segments sequentially. * * @throws IOException */ public SearcherManager(Directory dir, SearcherWarmer warmer, ExecutorService es) throws IOException { this.es = es; this.warmer = warmer; currentSearcher = new IndexSearcher(IndexReader.open(dir, true), es); } /** * You must call this, periodically, to perform a reopen. This calls * {@link IndexReader#openIfChanged(IndexReader)} with the underlying reader, and if that returns a * new reader, it's warmed (if you provided a {@link SearcherWarmer} and then * swapped into production. * * <p> * <b>Threads</b>: it's fine for more than one thread to call this at once. * Only the first thread will attempt the reopen; subsequent threads will see * that another thread is already handling reopen and will return immediately. * Note that this means if another thread is already reopening then subsequent * threads will return right away without waiting for the reader reopen to * complete. * </p> * * <p> * This method returns true if a new reader was in fact opened or * if the current searcher has no pending changes. * </p> */ public boolean maybeReopen() throws IOException { ensureOpen(); // Ensure only 1 thread does reopen at once; other // threads just return immediately: if (reopenLock.tryAcquire()) { try { // IR.openIfChanged preserves NRT and applyDeletes // in the newly returned reader: final IndexReader newReader = IndexReader.openIfChanged(currentSearcher.getIndexReader()); if (newReader != null) { final IndexSearcher newSearcher = new IndexSearcher(newReader, es); boolean success = false; try { if (warmer != null) { warmer.warm(newSearcher); } swapSearcher(newSearcher); success = true; } finally { if (!success) { release(newSearcher); } } } return true; } finally { reopenLock.release(); } } else { return false; } } /** * Returns <code>true</code> if no changes have occured since this searcher * ie. reader was opened, otherwise <code>false</code>. * @see IndexReader#isCurrent() */ public boolean isSearcherCurrent() throws CorruptIndexException, IOException { final IndexSearcher searcher = acquire(); try { return searcher.getIndexReader().isCurrent(); } finally { release(searcher); } } /** * Release the searcher previously obtained with {@link #acquire}. * * <p> * <b>NOTE</b>: it's safe to call this after {@link #close}. */ public void release(IndexSearcher searcher) throws IOException { assert searcher != null; searcher.getIndexReader().decRef(); } /** * Close this SearcherManager to future searching. Any searches still in * process in other threads won't be affected, and they should still call * {@link #release} after they are done. */ 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); } } /** * Obtain the current IndexSearcher. You must match every call to acquire with * one call to {@link #release}; it's best to do so in a finally clause. */ public IndexSearcher acquire() { IndexSearcher searcher; do { if ((searcher = currentSearcher) == null) { throw new AlreadyClosedException("this SearcherManager is closed"); } } while (!searcher.getIndexReader().tryIncRef()); return searcher; } private void ensureOpen() { if (currentSearcher == null) { throw new AlreadyClosedException("this SearcherManager is closed"); } } private synchronized void swapSearcher(IndexSearcher newSearcher) throws IOException { ensureOpen(); final IndexSearcher oldSearcher = currentSearcher; currentSearcher = newSearcher; release(oldSearcher); } }