1 package org.apache.lucene.search;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.io.IOException;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Semaphore;
24 import org.apache.lucene.index.CorruptIndexException;
25 import org.apache.lucene.index.IndexReader;
26 import org.apache.lucene.index.IndexWriter;
27 import org.apache.lucene.search.NRTManager; // javadocs
28 import org.apache.lucene.search.IndexSearcher; // javadocs
29 import org.apache.lucene.store.AlreadyClosedException;
30 import org.apache.lucene.store.Directory;
33 * Utility class to safely share {@link IndexSearcher} instances across multiple
34 * threads, while periodically reopening. This class ensures each searcher is
35 * closed only once all threads have finished using it.
38 * Use {@link #acquire} to obtain the current searcher, and {@link #release} to
39 * release it, like this:
41 * <pre class="prettyprint">
42 * IndexSearcher s = manager.acquire();
44 * // Do searching, doc retrieval, etc. with s
48 * // Do not use s after this!
53 * In addition you should periodically call {@link #maybeReopen}. While it's
54 * possible to call this just before running each query, this is discouraged
55 * since it penalizes the unlucky queries that do the reopen. It's better to use
56 * a separate background thread, that periodically calls maybeReopen. Finally,
57 * be sure to call {@link #close} once you are done.
60 * <b>NOTE</b>: if you have an {@link IndexWriter}, it's better to use
61 * {@link NRTManager} since that class pulls near-real-time readers from the
64 * @lucene.experimental
67 public final class SearcherManager {
69 private volatile IndexSearcher currentSearcher;
70 private final ExecutorService es;
71 private final SearcherWarmer warmer;
72 private final Semaphore reopenLock = new Semaphore(1);
75 * Creates and returns a new SearcherManager from the given {@link IndexWriter}.
76 * @param writer the IndexWriter to open the IndexReader from.
77 * @param applyAllDeletes If <code>true</code>, all buffered deletes will
78 * be applied (made visible) in the {@link IndexSearcher} / {@link IndexReader}.
79 * If <code>false</code>, the deletes may or may not be applied, but remain buffered
80 * (in IndexWriter) so that they will be applied in the future.
81 * Applying deletes can be costly, so if your app can tolerate deleted documents
82 * being returned you might gain some performance by passing <code>false</code>.
83 * See {@link IndexReader#openIfChanged(IndexReader, IndexWriter, boolean)}.
84 * @param warmer An optional {@link SearcherWarmer}. Pass
85 * <code>null</code> if you don't require the searcher to warmed
86 * before going live. If this is <code>non-null</code> then a
87 * merged segment warmer is installed on the
88 * provided IndexWriter's config.
89 * @param es An optional {@link ExecutorService} so different segments can
90 * be searched concurrently (see {@link
91 * IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}. Pass <code>null</code>
92 * to search segments sequentially.
96 public SearcherManager(IndexWriter writer, boolean applyAllDeletes,
97 final SearcherWarmer warmer, final ExecutorService es) throws IOException {
100 currentSearcher = new IndexSearcher(IndexReader.open(writer, applyAllDeletes));
101 if (warmer != null) {
102 writer.getConfig().setMergedSegmentWarmer(
103 new IndexWriter.IndexReaderWarmer() {
105 public void warm(IndexReader reader) throws IOException {
106 warmer.warm(new IndexSearcher(reader, es));
113 * Creates and returns a new SearcherManager from the given {@link Directory}.
114 * @param dir the directory to open the IndexReader on.
115 * @param warmer An optional {@link SearcherWarmer}. Pass
116 * <code>null</code> if you don't require the searcher to warmed
117 * before going live. If this is <code>non-null</code> then a
118 * merged segment warmer is installed on the
119 * provided IndexWriter's config.
120 * @param es And optional {@link ExecutorService} so different segments can
121 * be searched concurrently (see {@link
122 * IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}. Pass <code>null</code>
123 * to search segments sequentially.
125 * @throws IOException
127 public SearcherManager(Directory dir, SearcherWarmer warmer,
128 ExecutorService es) throws IOException {
130 this.warmer = warmer;
131 currentSearcher = new IndexSearcher(IndexReader.open(dir, true), es);
135 * You must call this, periodically, to perform a reopen. This calls
136 * {@link IndexReader#openIfChanged(IndexReader)} with the underlying reader, and if that returns a
137 * new reader, it's warmed (if you provided a {@link SearcherWarmer} and then
138 * swapped into production.
141 * <b>Threads</b>: it's fine for more than one thread to call this at once.
142 * Only the first thread will attempt the reopen; subsequent threads will see
143 * that another thread is already handling reopen and will return immediately.
144 * Note that this means if another thread is already reopening then subsequent
145 * threads will return right away without waiting for the reader reopen to
150 * This method returns true if a new reader was in fact opened or
151 * if the current searcher has no pending changes.
154 public boolean maybeReopen() throws IOException {
156 // Ensure only 1 thread does reopen at once; other
157 // threads just return immediately:
158 if (reopenLock.tryAcquire()) {
160 // IR.openIfChanged preserves NRT and applyDeletes
161 // in the newly returned reader:
162 final IndexReader newReader = IndexReader.openIfChanged(currentSearcher.getIndexReader());
163 if (newReader != null) {
164 final IndexSearcher newSearcher = new IndexSearcher(newReader, es);
165 boolean success = false;
167 if (warmer != null) {
168 warmer.warm(newSearcher);
170 swapSearcher(newSearcher);
174 release(newSearcher);
180 reopenLock.release();
188 * Returns <code>true</code> if no changes have occured since this searcher
189 * ie. reader was opened, otherwise <code>false</code>.
190 * @see IndexReader#isCurrent()
192 public boolean isSearcherCurrent() throws CorruptIndexException,
194 final IndexSearcher searcher = acquire();
196 return searcher.getIndexReader().isCurrent();
203 * Release the searcher previously obtained with {@link #acquire}.
206 * <b>NOTE</b>: it's safe to call this after {@link #close}.
208 public void release(IndexSearcher searcher) throws IOException {
209 assert searcher != null;
210 searcher.getIndexReader().decRef();
214 * Close this SearcherManager to future searching. Any searches still in
215 * process in other threads won't be affected, and they should still call
216 * {@link #release} after they are done.
218 public synchronized void close() throws IOException {
219 if (currentSearcher != null) {
220 // make sure we can call this more than once
221 // closeable javadoc says:
222 // if this is already closed then invoking this method has no effect.
228 * Obtain the current IndexSearcher. You must match every call to acquire with
229 * one call to {@link #release}; it's best to do so in a finally clause.
231 public IndexSearcher acquire() {
232 IndexSearcher searcher;
234 if ((searcher = currentSearcher) == null) {
235 throw new AlreadyClosedException("this SearcherManager is closed");
237 } while (!searcher.getIndexReader().tryIncRef());
241 private void ensureOpen() {
242 if (currentSearcher == null) {
243 throw new AlreadyClosedException("this SearcherManager is closed");
247 private synchronized void swapSearcher(IndexSearcher newSearcher) throws IOException {
249 final IndexSearcher oldSearcher = currentSearcher;
250 currentSearcher = newSearcher;
251 release(oldSearcher);