You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2010/12/18 20:14:56 UTC
svn commit: r1050697 - in /lucene/dev/trunk/lucene/src:
java/org/apache/lucene/index/ test/org/apache/lucene/
test/org/apache/lucene/search/ test/org/apache/lucene/util/
Author: rmuir
Date: Sat Dec 18 19:14:55 2010
New Revision: 1050697
URL: http://svn.apache.org/viewvc?rev=1050697&view=rev
Log:
LUCENE-2819: prevent 'collateral damage' from threads in tests, warn about thread resource leaks
Modified:
lucene/dev/trunk/lucene/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java
lucene/dev/trunk/lucene/src/test/org/apache/lucene/TestMergeSchedulerExternal.java
lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiSearcher.java
lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestParallelMultiSearcher.java
lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestSort.java
lucene/dev/trunk/lucene/src/test/org/apache/lucene/util/LuceneTestCase.java
Modified: lucene/dev/trunk/lucene/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java (original)
+++ lucene/dev/trunk/lucene/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java Sat Dec 18 19:14:55 2010
@@ -69,13 +69,6 @@ public class ConcurrentMergeScheduler ex
protected IndexWriter writer;
protected int mergeThreadCount;
- public ConcurrentMergeScheduler() {
- if (allInstances != null) {
- // Only for testing
- addMyself();
- }
- }
-
/** Sets the max # simultaneous merge threads that should
* be running at once. This must be <= {@link
* #setMaxMergeCount}. */
@@ -431,7 +424,6 @@ public class ConcurrentMergeScheduler ex
if (!suppressExceptions) {
// suppressExceptions is normally only set during
// testing.
- anyExceptions = true;
handleMergeException(exc);
}
}
@@ -471,48 +463,6 @@ public class ConcurrentMergeScheduler ex
throw new MergePolicy.MergeException(exc, dir);
}
- static boolean anyExceptions = false;
-
- /** Used for testing */
- public static boolean anyUnhandledExceptions() {
- if (allInstances == null) {
- throw new RuntimeException("setTestMode() was not called; often this is because your test case's setUp method fails to call super.setUp in LuceneTestCase");
- }
- synchronized(allInstances) {
- final int count = allInstances.size();
- // Make sure all outstanding threads are done so we see
- // any exceptions they may produce:
- for(int i=0;i<count;i++)
- allInstances.get(i).sync();
- boolean v = anyExceptions;
- anyExceptions = false;
- return v;
- }
- }
-
- public static void clearUnhandledExceptions() {
- synchronized(allInstances) {
- anyExceptions = false;
- }
- }
-
- /** Used for testing */
- private void addMyself() {
- synchronized(allInstances) {
- final int size = allInstances.size();
- int upto = 0;
- for(int i=0;i<size;i++) {
- final ConcurrentMergeScheduler other = allInstances.get(i);
- if (!(other.closed && 0 == other.mergeThreadCount()))
- // Keep this one for now: it still has threads or
- // may spawn new threads
- allInstances.set(upto++, other);
- }
- allInstances.subList(upto, allInstances.size()).clear();
- allInstances.add(this);
- }
- }
-
private boolean suppressExceptions;
/** Used for testing */
@@ -524,10 +474,4 @@ public class ConcurrentMergeScheduler ex
void clearSuppressExceptions() {
suppressExceptions = false;
}
-
- /** Used for testing */
- private static List<ConcurrentMergeScheduler> allInstances;
- public static void setTestMode() {
- allInstances = new ArrayList<ConcurrentMergeScheduler>();
- }
}
Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/TestMergeSchedulerExternal.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/TestMergeSchedulerExternal.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/TestMergeSchedulerExternal.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/TestMergeSchedulerExternal.java Sat Dec 18 19:14:55 2010
@@ -105,6 +105,5 @@ public class TestMergeSchedulerExternal
assertTrue(mergeCalled);
assertTrue(excCalled);
dir.close();
- assertTrue(ConcurrentMergeScheduler.anyUnhandledExceptions());
}
}
Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiSearcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiSearcher.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiSearcher.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiSearcher.java Sat Dec 18 19:14:55 2010
@@ -277,6 +277,7 @@ public class TestMultiSearcher extends L
indexSearcher2.close();
ramDirectory1.close();
ramDirectory2.close();
+ searcher.close();
}
/* uncomment this when the highest score is always normalized to 1.0, even when it was < 1.0
Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestParallelMultiSearcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestParallelMultiSearcher.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestParallelMultiSearcher.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestParallelMultiSearcher.java Sat Dec 18 19:14:55 2010
@@ -18,16 +18,34 @@ package org.apache.lucene.search;
*/
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.lucene.util._TestUtil;
/**
* Unit tests for the ParallelMultiSearcher
*/
public class TestParallelMultiSearcher extends TestMultiSearcher {
+ List<ExecutorService> pools = new ArrayList<ExecutorService>();
+
+ @Override
+ public void tearDown() throws Exception {
+ for (ExecutorService exec : pools)
+ exec.awaitTermination(1000, TimeUnit.MILLISECONDS);
+ pools.clear();
+ super.tearDown();
+ }
@Override
protected MultiSearcher getMultiSearcherInstance(Searcher[] searchers)
throws IOException {
- return new ParallelMultiSearcher(searchers);
+ ExecutorService exec = Executors.newFixedThreadPool(_TestUtil.nextInt(random, 2, 8));
+ pools.add(exec);
+ return new ParallelMultiSearcher(exec, searchers);
}
}
Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestSort.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestSort.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestSort.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestSort.java Sat Dec 18 19:14:55 2010
@@ -25,6 +25,10 @@ import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
@@ -51,6 +55,7 @@ import org.apache.lucene.store.LockObtai
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.DocIdBitSet;
import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util._TestUtil;
/**
* Unit tests for sorting code.
@@ -611,7 +616,8 @@ public class TestSort extends LuceneTest
// Don't close the multiSearcher. it would close the full searcher too!
// Do the same for a ParallelMultiSearcher
- Searcher parallelSearcher=new ParallelMultiSearcher (full);
+ ExecutorService exec = Executors.newFixedThreadPool(_TestUtil.nextInt(random, 2, 8));
+ Searcher parallelSearcher=new ParallelMultiSearcher (exec, full);
sort.setSort (new SortField ("int", SortField.INT),
new SortField ("string", SortField.STRING),
@@ -622,7 +628,8 @@ public class TestSort extends LuceneTest
new SortField ("string", SortField.STRING),
new SortField ("float", SortField.FLOAT, true) );
assertMatches (parallelSearcher, queryG, sort, "ZYXW");
- // Don't close the parallelSearcher. it would close the full searcher too!
+ parallelSearcher.close();
+ exec.awaitTermination(1000, TimeUnit.MILLISECONDS);
}
// test sorts using a series of fields
@@ -690,8 +697,11 @@ public class TestSort extends LuceneTest
// test a variety of sorts using a parallel multisearcher
public void testParallelMultiSort() throws Exception {
- Searcher searcher = new ParallelMultiSearcher (searchX, searchY);
+ ExecutorService exec = Executors.newFixedThreadPool(_TestUtil.nextInt(random, 2, 8));
+ Searcher searcher = new ParallelMultiSearcher (exec, searchX, searchY);
runMultiSorts(searcher, false);
+ searcher.close();
+ exec.awaitTermination(1000, TimeUnit.MILLISECONDS);
}
// test that the relevancy scores are the same even if
Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/util/LuceneTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/util/LuceneTestCase.java?rev=1050697&r1=1050696&r2=1050697&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/util/LuceneTestCase.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/util/LuceneTestCase.java Sat Dec 18 19:14:55 2010
@@ -336,6 +336,7 @@ public abstract class LuceneTestCase ext
@AfterClass
public static void afterClassLuceneTestCaseJ4() {
+ threadCleanup("test class");
String codecDescription;
CodecProvider cp = CodecProvider.getDefault();
@@ -434,13 +435,13 @@ public abstract class LuceneTestCase ext
savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
+ testsFailed = true;
uncaughtExceptions.add(new UncaughtExceptionEntry(t, e));
if (savedUncaughtExceptionHandler != null)
savedUncaughtExceptionHandler.uncaughtException(t, e);
}
});
- ConcurrentMergeScheduler.setTestMode();
savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount();
}
@@ -468,7 +469,10 @@ public abstract class LuceneTestCase ext
public void tearDown() throws Exception {
assertTrue("ensure your setUp() calls super.setUp()!!!", setup);
setup = false;
+ Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler);
BooleanQuery.setMaxClauseCount(savedBoolMaxClauseCount);
+ if (!getClass().getName().startsWith("org.apache.solr"))
+ threadCleanup("test method: '" + getName() + "'");
try {
if (!uncaughtExceptions.isEmpty()) {
@@ -496,19 +500,61 @@ public abstract class LuceneTestCase ext
// isolated in distinct test methods
assertSaneFieldCaches(getTestLabel());
- if (ConcurrentMergeScheduler.anyUnhandledExceptions()) {
- // Clear the failure so that we don't just keep
- // failing subsequent test cases
- ConcurrentMergeScheduler.clearUnhandledExceptions();
- fail("ConcurrentMergeScheduler hit unhandled exceptions");
- }
} finally {
purgeFieldCache(FieldCache.DEFAULT);
}
-
- Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler);
}
+ private final static int THREAD_STOP_GRACE_MSEC = 1000;
+ // jvm-wide list of 'rogue threads' we found, so they only get reported once.
+ private final static IdentityHashMap<Thread,Boolean> rogueThreads = new IdentityHashMap<Thread,Boolean>();
+
+ private static void threadCleanup(String context) {
+ // we will only actually fail() after all cleanup has happened!
+ boolean shouldFail = false;
+
+ // educated guess
+ Thread[] stillRunning = new Thread[Thread.activeCount()+1];
+ int threadCount = 0;
+ int rogueCount = 0;
+
+ if ((threadCount = Thread.enumerate(stillRunning)) > 1) {
+ while (threadCount == stillRunning.length) {
+ // truncated response
+ stillRunning = new Thread[stillRunning.length*2];
+ threadCount = Thread.enumerate(stillRunning);
+ }
+
+ for (int i = 0; i < threadCount; i++) {
+ Thread t = stillRunning[i];
+ // TODO: turn off our exception handler for these leftover threads... does this work?
+ if (t != Thread.currentThread())
+ t.setUncaughtExceptionHandler(null);
+ if (t.isAlive() &&
+ !rogueThreads.containsKey(t) &&
+ t != Thread.currentThread() &&
+ // TODO: TimeLimitingCollector starts a thread statically.... WTF?!
+ !t.getName().equals("TimeLimitedCollector timer thread")) {
+ System.err.println("WARNING: " + context + " left thread running: " + t);
+ rogueThreads.put(t, true);
+ shouldFail = true;
+ rogueCount++;
+ // try to stop the thread:
+ t.interrupt();
+ try {
+ t.join(THREAD_STOP_GRACE_MSEC);
+ } catch (InterruptedException e) { e.printStackTrace(); }
+ }
+ }
+ }
+
+ if (shouldFail && !testsFailed /* don't be loud if the test failed, maybe it didnt join() etc */) {
+ // TODO: we can't fail until we fix contrib and solr
+ //fail("test '" + getName() + "' left " + rogueCount + " thread(s) running");
+ System.err.println("RESOURCE LEAK: " + context + " left " + rogueCount + " thread(s) running");
+ }
+ }
+
/**
* Asserts that FieldCacheSanityChecker does not detect any
* problems with FieldCache.DEFAULT.