pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / SearcherManager.java
1 package org.apache.lucene.search;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 import java.io.IOException;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Semaphore;
23
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;
31
32 /**
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.
36  * 
37  * <p>
38  * Use {@link #acquire} to obtain the current searcher, and {@link #release} to
39  * release it, like this:
40  * 
41  * <pre class="prettyprint">
42  * IndexSearcher s = manager.acquire();
43  * try {
44  *   // Do searching, doc retrieval, etc. with s
45  * } finally {
46  *   manager.release(s);
47  * }
48  * // Do not use s after this!
49  * s = null;
50  * </pre>
51  * 
52  * <p>
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.
58  * 
59  * <p>
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
62  * IndexWriter.
63  * 
64  * @lucene.experimental
65  */
66
67 public final class SearcherManager {
68
69   private volatile IndexSearcher currentSearcher;
70   private final ExecutorService es;
71   private final SearcherWarmer warmer;
72   private final Semaphore reopenLock = new Semaphore(1);
73   
74   /**
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.
93    *        
94    * @throws IOException
95    */
96   public SearcherManager(IndexWriter writer, boolean applyAllDeletes,
97       final SearcherWarmer warmer, final ExecutorService es) throws IOException {
98     this.es = es;
99     this.warmer = warmer;
100     currentSearcher = new IndexSearcher(IndexReader.open(writer, applyAllDeletes));
101     if (warmer != null) {
102       writer.getConfig().setMergedSegmentWarmer(
103           new IndexWriter.IndexReaderWarmer() {
104             @Override
105             public void warm(IndexReader reader) throws IOException {
106               warmer.warm(new IndexSearcher(reader, es));
107             }
108           });
109     }
110   }
111
112   /**
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.
124    *        
125    * @throws IOException
126    */
127   public SearcherManager(Directory dir, SearcherWarmer warmer,
128       ExecutorService es) throws IOException {
129     this.es = es;
130     this.warmer = warmer;
131     currentSearcher = new IndexSearcher(IndexReader.open(dir, true), es);
132   }
133
134   /**
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.
139    * 
140    * <p>
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
146    * complete.
147    * </p>
148    * 
149    * <p>
150    * This method returns true if a new reader was in fact opened or 
151    * if the current searcher has no pending changes.
152    * </p>
153    */
154   public boolean maybeReopen() throws IOException {
155     ensureOpen();
156     // Ensure only 1 thread does reopen at once; other
157     // threads just return immediately:
158     if (reopenLock.tryAcquire()) {
159       try {
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;
166           try {
167             if (warmer != null) {
168               warmer.warm(newSearcher);
169             }
170             swapSearcher(newSearcher);
171             success = true;
172           } finally {
173             if (!success) {
174               release(newSearcher);
175             }
176           }
177         }
178         return true;
179       } finally {
180         reopenLock.release();
181       }
182     } else {
183       return false;
184     }
185   }
186   
187   /**
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() 
191    */
192   public boolean isSearcherCurrent() throws CorruptIndexException,
193       IOException {
194     final IndexSearcher searcher = acquire();
195     try {
196       return searcher.getIndexReader().isCurrent();
197     } finally {
198       release(searcher);
199     }
200   }
201
202   /**
203    * Release the searcher previously obtained with {@link #acquire}.
204    * 
205    * <p>
206    * <b>NOTE</b>: it's safe to call this after {@link #close}.
207    */
208   public void release(IndexSearcher searcher) throws IOException {
209     assert searcher != null;
210     searcher.getIndexReader().decRef();
211   }
212
213   /**
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.
217    */
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.
223       swapSearcher(null);
224     }
225   }
226
227   /**
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.
230    */
231   public IndexSearcher acquire() {
232     IndexSearcher searcher;
233     do {
234       if ((searcher = currentSearcher) == null) {
235         throw new AlreadyClosedException("this SearcherManager is closed");
236       }
237     } while (!searcher.getIndexReader().tryIncRef());
238     return searcher;
239   }
240
241   private void ensureOpen() {
242     if (currentSearcher == null) {
243       throw new AlreadyClosedException("this SearcherManager is closed");
244     }
245   }
246
247   private synchronized void swapSearcher(IndexSearcher newSearcher) throws IOException {
248     ensureOpen();
249     final IndexSearcher oldSearcher = currentSearcher;
250     currentSearcher = newSearcher;
251     release(oldSearcher);
252   }
253  
254 }