Index: src/test/org/apache/lucene/TestMergeSchedulerExternal.java =================================================================== --- src/test/org/apache/lucene/TestMergeSchedulerExternal.java (revision 1050282) +++ src/test/org/apache/lucene/TestMergeSchedulerExternal.java (working copy) @@ -105,6 +105,5 @@ assertTrue(mergeCalled); assertTrue(excCalled); dir.close(); - assertTrue(ConcurrentMergeScheduler.anyUnhandledExceptions()); } } Index: src/test/org/apache/lucene/index/TestIndexWriterOnDiskFull.java =================================================================== --- src/test/org/apache/lucene/index/TestIndexWriterOnDiskFull.java (revision 1050282) +++ src/test/org/apache/lucene/index/TestIndexWriterOnDiskFull.java (working copy) @@ -209,6 +209,7 @@ System.out.println("TEST: iter=" + iter); // Start with 100 bytes more than we are currently using: + // nocommit make eviler long diskFree = diskUsage+100; int method = iter; @@ -226,12 +227,16 @@ } while(!done) { + if (VERBOSE) { + System.out.println("TEST: cycle..."); + } // Make a new dir that will enforce disk usage: MockDirectoryWrapper dir = new MockDirectoryWrapper(random, new RAMDirectory(startDir)); writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer()).setOpenMode(OpenMode.APPEND)); IOException err = null; - + writer.setInfoStream(VERBOSE ? System.out : null); + MergeScheduler ms = writer.getConfig().getMergeScheduler(); for(int x=0;x<2;x++) { if (ms instanceof ConcurrentMergeScheduler) Index: src/test/org/apache/lucene/util/LuceneTestCase.java =================================================================== --- src/test/org/apache/lucene/util/LuceneTestCase.java (revision 1050282) +++ src/test/org/apache/lucene/util/LuceneTestCase.java (working copy) @@ -440,7 +440,6 @@ } }); - ConcurrentMergeScheduler.setTestMode(); savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount(); } @@ -468,7 +467,9 @@ public void tearDown() throws Exception { assertTrue("ensure your setUp() calls super.setUp()!!!", setup); setup = false; + Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler); BooleanQuery.setMaxClauseCount(savedBoolMaxClauseCount); + threadCleanup(); try { if (!uncaughtExceptions.isEmpty()) { @@ -496,19 +497,71 @@ // 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); } + } + + 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 rogueThreads = new IdentityHashMap(); + + private void threadCleanup() { + /* + * If there are already failures, we will be a bit nicer about warnings. + * This is just to reduce noise, e.g. the test properly calls join(), + * but the test failed before it could run. + */ + boolean failed = testsFailed || !uncaughtExceptions.isEmpty(); - Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler); + // 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; + + if ((threadCount = Thread.enumerate(stillRunning)) > 1) { + while (threadCount == stillRunning.length) { + // truncated response + stillRunning = new Thread[stillRunning.length*2]; + threadCount = Thread.enumerate(stillRunning); + } + + if (!failed) { + for (int i = 0; i < threadCount; i++) { + Thread t = stillRunning[i]; + if (!rogueThreads.containsKey(t) && t != Thread.currentThread()) + shouldFail = true; + } + } + + // Thread murder + for (int i = 0; i < threadCount; i++) { + Thread t = stillRunning[i]; + if (t.isAlive() && t != Thread.currentThread()) { + t.interrupt(); + try { + t.join(THREAD_STOP_GRACE_MSEC); + } catch (InterruptedException e) { e.printStackTrace(); } + } + } + + // shut down the jvm if we find any we can't kill? + for (int i = 0; i < threadCount; i++) { + Thread t = stillRunning[i]; + if (t.isAlive() && t != Thread.currentThread() && !rogueThreads.containsKey(t)) { + rogueThreads.put(t, true); + fail("test '" + getName() + "' spawned thread that refuses to die!: " + t); + } + } + } + + if (shouldFail) { + fail("test '" + getName() + "' passed but left " + (threadCount - rogueThreads.size() - 1) + " threads running"); + } } - + /** * Asserts that FieldCacheSanityChecker does not detect any * problems with FieldCache.DEFAULT. Index: src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java =================================================================== --- src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java (revision 1050282) +++ src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java (working copy) @@ -69,13 +69,6 @@ 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 @@ if (!suppressExceptions) { // suppressExceptions is normally only set during // testing. - anyExceptions = true; handleMergeException(exc); } } @@ -471,48 +463,6 @@ 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 allInstances; - public static void setTestMode() { - allInstances = new ArrayList(); - } }