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.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicBoolean;
29 import org.apache.lucene.analysis.MockAnalyzer;
30 import org.apache.lucene.document.Document;
31 import org.apache.lucene.index.ConcurrentMergeScheduler;
32 import org.apache.lucene.index.IndexWriter;
33 import org.apache.lucene.index.Term;
34 import org.apache.lucene.index.ThreadedIndexingAndSearchingTestCase;
35 import org.apache.lucene.store.AlreadyClosedException;
36 import org.apache.lucene.store.Directory;
37 import org.apache.lucene.util.NamedThreadFactory;
38 import org.apache.lucene.util._TestUtil;
40 public class TestSearcherManager extends ThreadedIndexingAndSearchingTestCase {
44 private SearcherLifetimeManager.Pruner pruner;
46 public void testSearcherManager() throws Exception {
47 pruner = new SearcherLifetimeManager.PruneByAge(TEST_NIGHTLY ? _TestUtil.nextInt(random, 1, 20) : 1);
48 runTest("TestSearcherManager");
52 protected IndexSearcher getFinalSearcher() throws Exception {
56 assertTrue(mgr.maybeReopen() || mgr.isSearcherCurrent());
60 private SearcherManager mgr;
61 private SearcherLifetimeManager lifetimeMGR;
62 private final List<Long> pastSearchers = new ArrayList<Long>();
63 private boolean isNRT;
66 protected void doAfterWriter(ExecutorService es) throws Exception {
67 final SearcherWarmer warmer = new SearcherWarmer() {
68 public void warm(IndexSearcher s) throws IOException {
69 TestSearcherManager.this.warmCalled = true;
70 s.search(new TermQuery(new Term("body", "united")), 10);
73 if (random.nextBoolean()) {
74 // TODO: can we randomize the applyAllDeletes? But
75 // somehow for final searcher we must apply
77 mgr = new SearcherManager(writer, true, warmer, es);
80 // SearcherManager needs to see empty commit:
82 mgr = new SearcherManager(dir, warmer, es);
86 lifetimeMGR = new SearcherLifetimeManager();
90 protected void doSearching(ExecutorService es, final long stopTime) throws Exception {
92 Thread reopenThread = new Thread() {
96 while(System.currentTimeMillis() < stopTime) {
97 Thread.sleep(_TestUtil.nextInt(random, 1, 100));
99 Thread.sleep(_TestUtil.nextInt(random, 1, 5));
100 if (mgr.maybeReopen()) {
101 lifetimeMGR.prune(pruner);
104 } catch (Throwable t) {
105 System.out.println("TEST: reopen thread hit exc");
106 t.printStackTrace(System.out);
108 throw new RuntimeException(t);
112 reopenThread.setDaemon(true);
113 reopenThread.start();
115 runSearchThreads(stopTime);
121 protected IndexSearcher getCurrentSearcher() throws Exception {
122 if (random.nextInt(10) == 7) {
123 // NOTE: not best practice to call maybeReopen
124 // synchronous to your search threads, but still we
125 // test as apps will presumably do this for
127 if (mgr.maybeReopen()) {
128 lifetimeMGR.prune(pruner);
132 IndexSearcher s = null;
134 synchronized(pastSearchers) {
135 while (pastSearchers.size() != 0 && random.nextDouble() < 0.25) {
136 // 1/4 of the time pull an old searcher, ie, simulate
137 // a user doing a follow-on action on a previous
138 // search (drilling down/up, clicking next/prev page,
140 final Long token = pastSearchers.get(random.nextInt(pastSearchers.size()));
141 s = lifetimeMGR.acquire(token);
143 // Searcher was pruned
144 pastSearchers.remove(token);
153 if (s.getIndexReader().numDocs() != 0) {
154 Long token = lifetimeMGR.record(s);
155 synchronized(pastSearchers) {
156 if (!pastSearchers.contains(token)) {
157 pastSearchers.add(token);
167 protected void releaseSearcher(IndexSearcher s) throws Exception {
168 s.getIndexReader().decRef();
172 protected void doClose() throws Exception {
173 assertTrue(warmCalled);
175 System.out.println("TEST: now close SearcherManager");
181 public void testIntermediateClose() throws IOException, InterruptedException {
182 Directory dir = newDirectory();
183 // Test can deadlock if we use SMS:
184 IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(
185 TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergeScheduler(new ConcurrentMergeScheduler()));
186 writer.addDocument(new Document());
188 final CountDownLatch awaitEnterWarm = new CountDownLatch(1);
189 final CountDownLatch awaitClose = new CountDownLatch(1);
190 final ExecutorService es = random.nextBoolean() ? null : Executors.newCachedThreadPool(new NamedThreadFactory("testIntermediateClose"));
191 final SearcherWarmer warmer = new SearcherWarmer() {
192 public void warm(IndexSearcher s) throws IOException {
194 awaitEnterWarm.countDown();
196 } catch (InterruptedException e) {
201 final SearcherManager searcherManager = random.nextBoolean() ? new SearcherManager(dir,
202 warmer, es) : new SearcherManager(writer, random.nextBoolean(), warmer, es);
203 IndexSearcher searcher = searcherManager.acquire();
205 assertEquals(1, searcher.getIndexReader().numDocs());
207 searcherManager.release(searcher);
209 writer.addDocument(new Document());
211 final AtomicBoolean success = new AtomicBoolean(false);
212 final AtomicBoolean triedReopen = new AtomicBoolean(false);
213 final Throwable[] exc = new Throwable[1];
214 Thread thread = new Thread(new Runnable() {
215 //@Override - not on java 5
218 triedReopen.set(true);
219 searcherManager.maybeReopen();
221 } catch (AlreadyClosedException e) {
223 } catch (Throwable e) {
225 // use success as the barrier here to make sure we see the write
232 awaitEnterWarm.await();
233 for (int i = 0; i < 2; i++) {
234 searcherManager.close();
236 awaitClose.countDown();
239 searcherManager.acquire();
240 fail("already closed");
241 } catch (AlreadyClosedException ex) {
244 assertFalse(success.get());
245 assertTrue(triedReopen.get());
246 assertNull("" + exc[0], exc[0]);
251 es.awaitTermination(1, TimeUnit.SECONDS);