LUCENE-1238: Fixed intermittent failures of TestTimeLimitedCollector.testTimeoutMultiThreaded.

git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@638568 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Doron Cohen 2008-03-18 21:08:08 +00:00
parent ea688c9363
commit 4bd9dba46f
3 changed files with 107 additions and 27 deletions

View File

@ -171,6 +171,10 @@ Build
Test Cases Test Cases
1. LUCENE-1238: Fixed intermittent failures of TestTimeLimitedCollector.testTimeoutMultiThreaded.
Within this fix, "greedy" flag was added to TimeLimitedCollector, to allow the wrapped
collector to collect also the last doc, after allowed-tTime passed. (Doron Cohen)
======================= Release 2.3.1 2008-02-22 ======================= ======================= Release 2.3.1 2008-02-22 =======================
Bug fixes Bug fixes

View File

@ -31,8 +31,16 @@ public class TimeLimitedCollector extends HitCollector {
*/ */
public static final int DEFAULT_RESOLUTION = 20; public static final int DEFAULT_RESOLUTION = 20;
/**
* Default for {@link #isGreedy()}.
* @see #isGreedy()
*/
public boolean DEFAULT_GREEDY = false;
private static long resolution = DEFAULT_RESOLUTION; private static long resolution = DEFAULT_RESOLUTION;
private boolean greedy = DEFAULT_GREEDY ;
private static class TimerThread extends Thread { private static class TimerThread extends Thread {
// NOTE: we can avoid explicit synchronization here for several reasons: // NOTE: we can avoid explicit synchronization here for several reasons:
@ -132,6 +140,11 @@ public class TimeLimitedCollector extends HitCollector {
private final long timeout; private final long timeout;
private final HitCollector hc; private final HitCollector hc;
/**
* Create a TimeLimitedCollector wrapper over another HitCollector with a specified timeout.
* @param hc the wrapped HitCollector
* @param timeAllowed max time allowed for collecting hits after which {@link TimeExceededException} is thrown
*/
public TimeLimitedCollector( final HitCollector hc, final long timeAllowed ) { public TimeLimitedCollector( final HitCollector hc, final long timeAllowed ) {
this.hc = hc; this.hc = hc;
t0 = TIMER_THREAD.getMilliseconds(); t0 = TIMER_THREAD.getMilliseconds();
@ -146,6 +159,10 @@ public class TimeLimitedCollector extends HitCollector {
public void collect( final int doc, final float score ) { public void collect( final int doc, final float score ) {
long time = TIMER_THREAD.getMilliseconds(); long time = TIMER_THREAD.getMilliseconds();
if( timeout < time) { if( timeout < time) {
if (greedy) {
//System.out.println(this+" greedy: before failing, collecting doc: "+doc+" "+(time-t0));
hc.collect( doc, score );
}
//System.out.println(this+" failing on: "+doc+" "+(time-t0)); //System.out.println(this+" failing on: "+doc+" "+(time-t0));
throw new TimeExceededException( timeout-t0, time-t0, doc ); throw new TimeExceededException( timeout-t0, time-t0, doc );
} }
@ -178,4 +195,25 @@ public class TimeLimitedCollector extends HitCollector {
public static void setResolution(long newResolution) { public static void setResolution(long newResolution) {
resolution = Math.max(newResolution,5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call. resolution = Math.max(newResolution,5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call.
} }
/**
* Checks if this time limited collector is greedy in collecting the last hit.
* A non greedy collector, upon a timeout, would throw a {@link TimeExceededException}
* without allowing the wrapped collector to collect current doc. A greedy one would
* first allow the wrapped hit collector to collect current doc and only then
* throw a {@link TimeExceededException}.
* @see #setGreedy(boolean)
*/
public boolean isGreedy() {
return greedy;
}
/**
* Sets whether this time limited collector is greedy.
* @param greedy true to make this time limited greedy
* @see #isGreedy()
*/
public void setGreedy(boolean greedy) {
this.greedy = greedy;
}
} }

View File

@ -17,7 +17,7 @@ package org.apache.lucene.search;
* limitations under the License. * limitations under the License.
*/ */
import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
@ -37,12 +37,12 @@ import java.util.BitSet;
*/ */
public class TestTimeLimitedCollector extends LuceneTestCase { public class TestTimeLimitedCollector extends LuceneTestCase {
private static final int SLOW_DOWN = 47; private static final int SLOW_DOWN = 47;
private static final int TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs. private static final long TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs.
// max time allowed is relaxed for multithreading tests. // max time allowed is relaxed for multithreading tests.
// the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000). // the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000).
// but this is not a real failure, just noise. // but this is not a real failure, just noise.
private static final double MULTI_THREAD_SLACK = 3; private static final double MULTI_THREAD_SLACK = 7;
private static final int N_DOCS = 3000; private static final int N_DOCS = 3000;
private static final int N_THREADS = 50; private static final int N_THREADS = 50;
@ -60,6 +60,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
*/ */
protected void setUp() throws Exception { protected void setUp() throws Exception {
final String docText[] = { final String docText[] = {
"docThatNeverMatchesSoWeCanRequireLastDocCollectedToBeGreaterThanZero",
"one blah three", "one blah three",
"one foo three multiOne", "one foo three multiOne",
"one foobar three multiThree", "one foobar three multiThree",
@ -69,7 +70,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
"blueberry pizza", "blueberry pizza",
}; };
Directory directory = new RAMDirectory(); Directory directory = new RAMDirectory();
IndexWriter iw = new IndexWriter(directory, new StandardAnalyzer(), true, MaxFieldLength.UNLIMITED); IndexWriter iw = new IndexWriter(directory, new WhitespaceAnalyzer(), true, MaxFieldLength.UNLIMITED);
for (int i=0; i<N_DOCS; i++) { for (int i=0; i<N_DOCS; i++) {
add(docText[i%docText.length], iw); add(docText[i%docText.length], iw);
@ -81,7 +82,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
for (int i = 0; i < docText.length; i++) { for (int i = 0; i < docText.length; i++) {
qtxt += ' ' + docText[i]; // large query so that search will be longer qtxt += ' ' + docText[i]; // large query so that search will be longer
} }
QueryParser queryParser = new QueryParser(FIELD_NAME, new StandardAnalyzer()); QueryParser queryParser = new QueryParser(FIELD_NAME, new WhitespaceAnalyzer());
query = queryParser.parse(qtxt); query = queryParser.parse(qtxt);
// warm the searcher // warm the searcher
@ -119,7 +120,8 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
totalResults = myHc.hitCount(); totalResults = myHc.hitCount();
myHc = new MyHitCollector(); myHc = new MyHitCollector();
HitCollector tlCollector = new TimeLimitedCollector(myHc, 3600000); // 1 hour long oneHour = 3600000;
HitCollector tlCollector = createTimedCollector(myHc, oneHour, false);
search(tlCollector); search(tlCollector);
totalTLCResults = myHc.hitCount(); totalTLCResults = myHc.hitCount();
} catch (Exception e) { } catch (Exception e) {
@ -128,37 +130,67 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
assertEquals( "Wrong number of results!", totalResults, totalTLCResults ); assertEquals( "Wrong number of results!", totalResults, totalTLCResults );
} }
private HitCollector createTimedCollector(MyHitCollector hc, long timeAllowed, boolean greedy) {
TimeLimitedCollector res = new TimeLimitedCollector(hc, timeAllowed);
res.setGreedy(greedy); // set to true to make sure at least one doc is collected.
return res;
}
/** /**
* Test that timeout is obtained, and soon enough! * Test that timeout is obtained, and soon enough!
*/ */
public void testTimeout() { public void testTimeoutGreedy() {
doTestTimeout(false); doTestTimeout(false, true);
} }
private void doTestTimeout(boolean multiThreaded) { /**
* Test that timeout is obtained, and soon enough!
*/
public void testTimeoutNotGreedy() {
doTestTimeout(false, false);
}
private void doTestTimeout(boolean multiThreaded, boolean greedy) {
// setup
MyHitCollector myHc = new MyHitCollector(); MyHitCollector myHc = new MyHitCollector();
myHc.setSlowDown(SLOW_DOWN); myHc.setSlowDown(SLOW_DOWN);
HitCollector tlCollector = new TimeLimitedCollector(myHc, TIME_ALLOWED); HitCollector tlCollector = createTimedCollector(myHc, TIME_ALLOWED, greedy);
TimeLimitedCollector.TimeExceededException exception = null; // search
TimeLimitedCollector.TimeExceededException timoutException = null;
try { try {
search(tlCollector); search(tlCollector);
} catch (TimeLimitedCollector.TimeExceededException x) { } catch (TimeLimitedCollector.TimeExceededException x) {
exception = x; timoutException = x;
} catch (Exception e) { } catch (Exception e) {
assertTrue("Unexpected exception: "+e, false); //==fail assertTrue("Unexpected exception: "+e, false); //==fail
} }
assertNotNull( "Timeout expected!", exception );
assertTrue( "no hits found!", myHc.hitCount() > 0 ); // must get exception
assertTrue( "last doc collected cannot be 0!", exception.getLastDocCollected() > 0 ); assertNotNull( "Timeout expected!", timoutException );
assertEquals( exception.getTimeAllowed(), TIME_ALLOWED);
assertTrue ( "elapsed="+exception.getTimeElapsed()+" <= (allowed-resolution)="+(TIME_ALLOWED-TimeLimitedCollector.getResolution()), // greediness affect last doc collected
exception.getTimeElapsed() > TIME_ALLOWED-TimeLimitedCollector.getResolution()); int exceptionDoc = timoutException.getLastDocCollected();
assertTrue ( "lastDoc="+exception.getLastDocCollected()+ int lastCollected = myHc.getLastDocCollected();
" ,&& allowed="+exception.getTimeAllowed() + assertTrue( "doc collected at timeout must be > 0!", exceptionDoc > 0 );
" ,&& elapsed="+exception.getTimeElapsed() + if (greedy) {
assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" != lastCollected="+lastCollected, exceptionDoc==lastCollected);
assertTrue("greedy, but no hits found!", myHc.hitCount() > 0 );
} else {
assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" not > lastCollected="+lastCollected, exceptionDoc>lastCollected);
}
// verify that elapsed time at exception is within valid limits
assertEquals( timoutException.getTimeAllowed(), TIME_ALLOWED);
// a) Not too early
assertTrue ( "elapsed="+timoutException.getTimeElapsed()+" <= (allowed-resolution)="+(TIME_ALLOWED-TimeLimitedCollector.getResolution()),
timoutException.getTimeElapsed() > TIME_ALLOWED-TimeLimitedCollector.getResolution());
// b) Not too late (this part might be problematic in a busy system, consider removing it if it raises false test failures.
assertTrue ( "lastDoc="+exceptionDoc+
" ,&& allowed="+timoutException.getTimeAllowed() +
" ,&& elapsed="+timoutException.getTimeElapsed() +
" >= " + maxTimeStr(multiThreaded), " >= " + maxTimeStr(multiThreaded),
exception.getTimeElapsed() < maxTime(multiThreaded)); timoutException.getTimeElapsed() < maxTime(multiThreaded));
} }
private long maxTime(boolean multiThreaded) { private long maxTime(boolean multiThreaded) {
@ -190,17 +222,17 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
long resolution = 20 * TimeLimitedCollector.DEFAULT_RESOLUTION; //400 long resolution = 20 * TimeLimitedCollector.DEFAULT_RESOLUTION; //400
TimeLimitedCollector.setResolution(resolution); TimeLimitedCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitedCollector.getResolution()); assertEquals(resolution, TimeLimitedCollector.getResolution());
doTestTimeout(false); doTestTimeout(false,true);
// decrease much and test // decrease much and test
resolution = 5; resolution = 5;
TimeLimitedCollector.setResolution(resolution); TimeLimitedCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitedCollector.getResolution()); assertEquals(resolution, TimeLimitedCollector.getResolution());
doTestTimeout(false); doTestTimeout(false,true);
// return to default and test // return to default and test
resolution = TimeLimitedCollector.DEFAULT_RESOLUTION; resolution = TimeLimitedCollector.DEFAULT_RESOLUTION;
TimeLimitedCollector.setResolution(resolution); TimeLimitedCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitedCollector.getResolution()); assertEquals(resolution, TimeLimitedCollector.getResolution());
doTestTimeout(false); doTestTimeout(false,true);
} finally { } finally {
TimeLimitedCollector.setResolution(TimeLimitedCollector.DEFAULT_RESOLUTION); TimeLimitedCollector.setResolution(TimeLimitedCollector.DEFAULT_RESOLUTION);
} }
@ -228,7 +260,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
threadArray[num] = new Thread() { threadArray[num] = new Thread() {
public void run() { public void run() {
if (withTimeout) { if (withTimeout) {
doTestTimeout(true); doTestTimeout(true,true);
} else { } else {
doTestSearch(); doTestSearch();
} }
@ -260,6 +292,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
{ {
private final BitSet bits = new BitSet(); private final BitSet bits = new BitSet();
private int slowdown = 0; private int slowdown = 0;
private int lastDocCollected = -1;
/** /**
* amount of time to wait on each collect to simulate a long iteration * amount of time to wait on each collect to simulate a long iteration
@ -278,12 +311,17 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
} }
} }
bits.set( doc ); bits.set( doc );
lastDocCollected = doc;
} }
public int hitCount() { public int hitCount() {
return bits.cardinality(); return bits.cardinality();
} }
public int getLastDocCollected() {
return lastDocCollected;
}
} }
} }