/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2004
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.container;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import junit.framework.TestCase;
import alma.acs.logging.ClientLogManager;
import alma.acs.testsupport.LogRecordCollectingLogger;
/**
* Tests the thread factory used by {@link ContainerServices#getThreadFactory()}, which can kill surviving user threads.
* @author hsommer
* created Apr 29, 2005 3:19:11 PM
*/
public class CleaningThreadFactoryTest extends TestCase {
private LogRecordCollectingLogger logger;
protected void setUp() throws Exception {
Logger delegate = ClientLogManager.getAcsLogManager().getLoggerForApplication(getName(), false);
logger = LogRecordCollectingLogger.getCollectingLogger("CleaningThreadFactoryTest-CollectingLogger-" + getName());
logger.setDelegateLogger(delegate);
logger.info("------------ setUp " + getName() + " --------------");
}
protected void tearDown() throws Exception {
logger.info("------------ tearDown " + getName() + " --------------");
logger.clearLogRecords();
ClientLogManager.getAcsLogManager().shutdown(true);
}
public void testNoOp() {
String factoryName = "ContainerTestThreadGroup-testNoOp";
CleaningDaemonThreadFactory threadFactory = new CleaningDaemonThreadFactory(factoryName, logger);
threadFactory.cleanUp();
}
/**
* Creates 4 threads such that they have different behavior and status when cleanUp is called on the thread factory:
* <ol>
* <li>thread that will have died neatly (the expected behavior of user threads)
* <li>thread that will be sleeping
* <li>thread that will be busy
* <li>thread that will not yet have been started
* </ol>
* This test verifies that
*/
public void testStoppingThreads() throws Exception {
String factoryName = "ContainerTestThreadGroup";
CleaningDaemonThreadFactory threadFactory = new CleaningDaemonThreadFactory(factoryName, logger);
final CountDownLatch synch1 = new CountDownLatch(1);
Runnable terminatedRunnable = new Runnable() {
public void run() {
synch1.countDown();
sleep(100);
logger.info("Short-sleeping test runnable started and stopped running in thread "
+ Thread.currentThread().getName());
}
};
final CountDownLatch synch2 = new CountDownLatch(1);
Runnable sleepingRunnable = new Runnable() {
public void run() {
synch2.countDown();
logger.info("Sleeping test runnable starts running in thread " + Thread.currentThread().getName());
// will be sleeping when cleanUp is called
sleep(20000);
}
};
final CountDownLatch synch3 = new CountDownLatch(1);
Runnable busyRunnable = new Runnable() {
public void run() {
synch3.countDown();
logger.info("Busy runnable starts running in thread " + Thread.currentThread().getName());
int i = 0;
while (true) {
i++;
}
}
};
Runnable virginRunnable = new Runnable() {
public void run() {
// should never be called.
logger.warning("virgin runnable should not have been executed!");
}
};
Thread t1 = threadFactory.newThread(terminatedRunnable);
ThreadGroup tg = t1.getThreadGroup();
assertEquals(factoryName, tg.getName());
assertFalse(tg.isDaemon());
assertFalse(tg.isDestroyed());
assertTrue(t1.isDaemon());
assertEquals(factoryName + "-1", t1.getName());
t1.start();
synch1.await(); // wait until thread is running
Thread t2 = threadFactory.newThread(sleepingRunnable);
assertEquals(tg, t2.getThreadGroup());
t2.start();
synch2.await(); // wait until thread is running
Thread t3 = threadFactory.newThread(busyRunnable);
// t3.setPriority(t3.getPriority()-1);
t3.start();
synch3.await(); // wait until thread is running
// this thread just gets constructed but not started
threadFactory.newThread(virginRunnable);
sleep(1000);
logger.clearLogRecords();
threadFactory.cleanUp();
// verify logged warnings about stopping threads t2 and t3
LogRecord[] logs = logger.getCollectedLogRecords();
if (logs.length != 3) {
String messages = "";
for (int i = 0; i < logs.length; i++) {
messages += logs[i].getMessage() + "\n";
}
fail("Expected 3 logs about thread termination from the thread factory, but got " + logs.length + ": \n" + messages);
}
else {
assertEquals(Level.WARNING, logs[0].getLevel());
assertEquals("Forcibly terminating surviving thread " + factoryName + "-2", logs[0].getMessage());
assertEquals("Thread " + factoryName + "-2 was interrupted while sleeping.", logs[1].getMessage());
assertEquals("Forcibly terminating surviving thread " + factoryName + "-3", logs[2].getMessage());
}
}
/**
* Verifies that a warning is logged when a user thread throws an otherwise uncaught exception.
*/
public void testUserThreadException() {
String factoryName = "StupidUserApplication";
CleaningDaemonThreadFactory threadFactory = new CleaningDaemonThreadFactory(factoryName, logger);
final String exMsg = "intended NPE from test application thread.";
Runnable errorCmd = new Runnable() {
public void run() {
sleep(500);
logger.info("Stupid user thread will now throw a NPE...");
throw new NullPointerException(exMsg);
}
};
Thread t = threadFactory.newThread(errorCmd);
logger.clearLogRecords();
t.start();
sleep(2000);
// verify that a warning was logged for the user thread exception
LogRecord[] logs = logger.getCollectedLogRecords();
assertEquals(2, logs.length);
LogRecord userExLogRecord = logs[1];
assertEquals(Level.WARNING, userExLogRecord.getLevel());
assertEquals("User thread '" + factoryName + "-1' terminated with error ", userExLogRecord.getMessage());
assertNotNull(userExLogRecord.getThrown());
assertEquals(exMsg, userExLogRecord.getThrown().getMessage());
}
public void testThreadStackDebugMessages() {
LogRecordCollectingLogger collectingLogger = LogRecordCollectingLogger.getCollectingLogger("testThreadStackDebugMessages_CollectingLogger");
System.setProperty(CleaningDaemonThreadFactory.LOG_THREAD_CREATION_CALLSTACK_PROPERTYNAME, "true");
CleaningDaemonThreadFactory tf = new CleaningDaemonThreadFactory("factoryWithStackTrace", collectingLogger);
Runnable dummyRunnable = new Runnable() {
public void run() {
}
};
tf.newThread(dummyRunnable);
LogRecord[] records = collectingLogger.getCollectedLogRecords();
assertEquals(1, records.length);
assertTrue(records[0].getMessage().startsWith("Created thread 'factoryWithStackTrace-1'. Call stack: alma.acs.container.CleaningThreadFactoryTest.testThreadStackDebugMessages(CleaningThreadFactoryTest.java:202) <- "));
System.setProperty(CleaningDaemonThreadFactory.LOG_THREAD_CREATION_CALLSTACK_PROPERTYNAME, "false");
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
logger.info("Thread " + Thread.currentThread().getName() + " was interrupted while sleeping.");
}
}
}