X-Git-Url: https://git.mdrn.pl/pylucene.git/blobdiff_plain/a2e61f0c04805cfcb8706176758d1283c7e3a55c..aaeed5504b982cf3545252ab528713250aa33eed:/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/SearcherManager.java diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/SearcherManager.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/SearcherManager.java new file mode 100644 index 0000000..3a0588b --- /dev/null +++ b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/SearcherManager.java @@ -0,0 +1,254 @@ +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. + * + *

+ * Use {@link #acquire} to obtain the current searcher, and {@link #release} to + * release it, like this: + * + *

+ * IndexSearcher s = manager.acquire();
+ * try {
+ *   // Do searching, doc retrieval, etc. with s
+ * } finally {
+ *   manager.release(s);
+ * }
+ * // Do not use s after this!
+ * s = null;
+ * 
+ * + *

+ * 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. + * + *

+ * NOTE: 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 true, all buffered deletes will + * be applied (made visible) in the {@link IndexSearcher} / {@link IndexReader}. + * If false, 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 false. + * See {@link IndexReader#openIfChanged(IndexReader, IndexWriter, boolean)}. + * @param warmer An optional {@link SearcherWarmer}. Pass + * null if you don't require the searcher to warmed + * before going live. If this is non-null 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 null + * 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 + * null if you don't require the searcher to warmed + * before going live. If this is non-null 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 null + * 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. + * + *

+ * Threads: 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. + *

+ * + *

+ * This method returns true if a new reader was in fact opened or + * if the current searcher has no pending changes. + *

+ */ + 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 true if no changes have occured since this searcher + * ie. reader was opened, otherwise false. + * @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}. + * + *

+ * NOTE: 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); + } + +}