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.BitSet;
23 import org.apache.lucene.analysis.MockAnalyzer;
24 import org.apache.lucene.document.Document;
25 import org.apache.lucene.document.Field;
26 import org.apache.lucene.index.IndexReader;
27 import org.apache.lucene.index.RandomIndexWriter;
28 import org.apache.lucene.queryParser.QueryParser;
29 import org.apache.lucene.search.TimeLimitingCollector.TimeExceededException;
30 import org.apache.lucene.store.Directory;
31 import org.apache.lucene.util.LuceneTestCase;
32 import org.apache.lucene.util.ThreadInterruptedException;
35 * Tests the {@link TimeLimitingCollector}. This test checks (1) search
36 * correctness (regardless of timeout), (2) expected timeout behavior,
37 * and (3) a sanity test with multiple searching threads.
39 public class TestTimeLimitingCollector extends LuceneTestCase {
40 private static final int SLOW_DOWN = 3;
41 private static final long TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs.
43 // max time allowed is relaxed for multithreading tests.
44 // the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000).
45 // but this is not a real failure, just noise.
46 private static final double MULTI_THREAD_SLACK = 7;
48 private static final int N_DOCS = 3000;
49 private static final int N_THREADS = 50;
51 private Searcher searcher;
52 private Directory directory;
53 private IndexReader reader;
55 private final String FIELD_NAME = "body";
59 * initializes searcher with a document set
62 public void setUp() throws Exception {
64 final String docText[] = {
65 "docThatNeverMatchesSoWeCanRequireLastDocCollectedToBeGreaterThanZero",
67 "one foo three multiOne",
68 "one foobar three multiThree",
74 directory = newDirectory();
75 RandomIndexWriter iw = new RandomIndexWriter(random, directory, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy()));
77 for (int i=0; i<N_DOCS; i++) {
78 add(docText[i%docText.length], iw);
80 reader = iw.getReader();
82 searcher = newSearcher(reader);
85 // start from 1, so that the 0th doc never matches
86 for (int i = 1; i < docText.length; i++) {
87 qtxt += ' ' + docText[i]; // large query so that search will be longer
89 QueryParser queryParser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, new MockAnalyzer(random));
90 query = queryParser.parse(qtxt);
93 searcher.search(query, null, 1000);
98 public void tearDown() throws Exception {
105 private void add(String value, RandomIndexWriter iw) throws IOException {
106 Document d = new Document();
107 d.add(newField(FIELD_NAME, value, Field.Store.NO, Field.Index.ANALYZED));
111 private void search(Collector collector) throws Exception {
112 searcher.search(query, collector);
116 * test search correctness with no timeout
118 public void testSearch() {
122 private void doTestSearch() {
123 int totalResults = 0;
124 int totalTLCResults = 0;
126 MyHitCollector myHc = new MyHitCollector();
128 totalResults = myHc.hitCount();
130 myHc = new MyHitCollector();
131 long oneHour = 3600000;
132 Collector tlCollector = createTimedCollector(myHc, oneHour, false);
134 totalTLCResults = myHc.hitCount();
135 } catch (Exception e) {
137 assertTrue("Unexpected exception: "+e, false); //==fail
139 assertEquals( "Wrong number of results!", totalResults, totalTLCResults );
142 private Collector createTimedCollector(MyHitCollector hc, long timeAllowed, boolean greedy) {
143 TimeLimitingCollector res = new TimeLimitingCollector(hc, timeAllowed);
144 res.setGreedy(greedy); // set to true to make sure at least one doc is collected.
149 * Test that timeout is obtained, and soon enough!
151 public void testTimeoutGreedy() {
152 doTestTimeout(false, true);
156 * Test that timeout is obtained, and soon enough!
158 public void testTimeoutNotGreedy() {
159 doTestTimeout(false, false);
162 private void doTestTimeout(boolean multiThreaded, boolean greedy) {
164 MyHitCollector myHc = new MyHitCollector();
165 myHc.setSlowDown(SLOW_DOWN);
166 Collector tlCollector = createTimedCollector(myHc, TIME_ALLOWED, greedy);
169 TimeExceededException timoutException = null;
172 } catch (TimeExceededException x) {
174 } catch (Exception e) {
175 assertTrue("Unexpected exception: "+e, false); //==fail
178 // must get exception
179 assertNotNull( "Timeout expected!", timoutException );
181 // greediness affect last doc collected
182 int exceptionDoc = timoutException.getLastDocCollected();
183 int lastCollected = myHc.getLastDocCollected();
184 assertTrue( "doc collected at timeout must be > 0!", exceptionDoc > 0 );
186 assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" != lastCollected="+lastCollected, exceptionDoc==lastCollected);
187 assertTrue("greedy, but no hits found!", myHc.hitCount() > 0 );
189 assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" not > lastCollected="+lastCollected, exceptionDoc>lastCollected);
192 // verify that elapsed time at exception is within valid limits
193 assertEquals( timoutException.getTimeAllowed(), TIME_ALLOWED);
195 assertTrue ( "elapsed="+timoutException.getTimeElapsed()+" <= (allowed-resolution)="+(TIME_ALLOWED-TimeLimitingCollector.getResolution()),
196 timoutException.getTimeElapsed() > TIME_ALLOWED-TimeLimitingCollector.getResolution());
198 // This part is problematic in a busy test system, so we just print a warning.
199 // We already verified that a timeout occurred, we just can't be picky about how long it took.
200 if (timoutException.getTimeElapsed() > maxTime(multiThreaded)) {
201 System.out.println("Informative: timeout exceeded (no action required: most probably just " +
202 " because the test machine is slower than usual): " +
203 "lastDoc="+exceptionDoc+
204 " ,&& allowed="+timoutException.getTimeAllowed() +
205 " ,&& elapsed="+timoutException.getTimeElapsed() +
206 " >= " + maxTimeStr(multiThreaded));
210 private long maxTime(boolean multiThreaded) {
211 long res = 2 * TimeLimitingCollector.getResolution() + TIME_ALLOWED + SLOW_DOWN; // some slack for less noise in this test
213 res *= MULTI_THREAD_SLACK; // larger slack
218 private String maxTimeStr(boolean multiThreaded) {
221 "2*resolution + TIME_ALLOWED + SLOW_DOWN = " +
222 "2*" + TimeLimitingCollector.getResolution() + " + " + TIME_ALLOWED + " + " + SLOW_DOWN +
225 s = MULTI_THREAD_SLACK + " * "+s;
227 return maxTime(multiThreaded) + " = " + s;
231 * Test timeout behavior when resolution is modified.
233 public void testModifyResolution() {
236 long resolution = 20 * TimeLimitingCollector.DEFAULT_RESOLUTION; //400
237 TimeLimitingCollector.setResolution(resolution);
238 assertEquals(resolution, TimeLimitingCollector.getResolution());
239 doTestTimeout(false,true);
240 // decrease much and test
242 TimeLimitingCollector.setResolution(resolution);
243 assertEquals(resolution, TimeLimitingCollector.getResolution());
244 doTestTimeout(false,true);
245 // return to default and test
246 resolution = TimeLimitingCollector.DEFAULT_RESOLUTION;
247 TimeLimitingCollector.setResolution(resolution);
248 assertEquals(resolution, TimeLimitingCollector.getResolution());
249 doTestTimeout(false,true);
251 TimeLimitingCollector.setResolution(TimeLimitingCollector.DEFAULT_RESOLUTION);
256 * Test correctness with multiple searching threads.
258 public void testSearchMultiThreaded() throws Exception {
259 doTestMultiThreads(false);
263 * Test correctness with multiple searching threads.
265 public void testTimeoutMultiThreaded() throws Exception {
266 doTestMultiThreads(true);
269 private void doTestMultiThreads(final boolean withTimeout) throws Exception {
270 Thread [] threadArray = new Thread[N_THREADS];
271 final BitSet success = new BitSet(N_THREADS);
272 for( int i = 0; i < threadArray.length; ++i ) {
274 threadArray[num] = new Thread() {
278 doTestTimeout(true,true);
282 synchronized(success) {
288 for( int i = 0; i < threadArray.length; ++i ) {
289 threadArray[i].start();
291 for( int i = 0; i < threadArray.length; ++i ) {
292 threadArray[i].join();
294 assertEquals("some threads failed!", N_THREADS,success.cardinality());
297 // counting collector that can slow down at collect().
298 private class MyHitCollector extends Collector {
299 private final BitSet bits = new BitSet();
300 private int slowdown = 0;
301 private int lastDocCollected = -1;
302 private int docBase = 0;
305 * amount of time to wait on each collect to simulate a long iteration
307 public void setSlowDown( int milliseconds ) {
308 slowdown = milliseconds;
311 public int hitCount() {
312 return bits.cardinality();
315 public int getLastDocCollected() {
316 return lastDocCollected;
320 public void setScorer(Scorer scorer) throws IOException {
321 // scorer is not needed
325 public void collect(final int doc) throws IOException {
326 int docId = doc + docBase;
329 Thread.sleep(slowdown);
330 } catch (InterruptedException ie) {
331 throw new ThreadInterruptedException(ie);
334 assert docId >= 0: " base=" + docBase + " doc=" + doc;
336 lastDocCollected = docId;
340 public void setNextReader(IndexReader reader, int base) {
345 public boolean acceptsDocsOutOfOrder() {