pylucene 3.5.0-3
[pylucene.git] / 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 (file)
index 0000000..3a0588b
--- /dev/null
@@ -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.
+ * 
+ * <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);
+  }
+}