/*
* 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.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import alma.acs.logging.AcsLogLevel;
/**
* Thread factory that remembers all threads it creates.
* Method <code>cleanUp</code> allows killing all threads.
* <p>
* A <code>ThreadGroup</code> is used to deal with all threads at a time. Its name is that given in the constructor.
* Even though Josh Bloch tells us in "Effective Java", Item 53, that thread groups are almost useless,
* we like the UncaughtExceptionHandler which JDK 1.4 only offers through ThreadGroups.
* TODO: revisit this in JDK 1.5 where Thread itself has this handler.
*
* @author hsommer
* created Mar 8, 2005 1:27:38 PM
*/
public class CleaningDaemonThreadFactory implements ThreadFactory
{
/**
* If this property is set to <code>true</code>, the creators of new threads will be identified through a INFO log showing the calling stack trace,
* which helps for example in finding out who created a thread but later fails to clean it up
* (see logs "Forcibly terminating surviving thread ...").
*/
public static final String LOG_THREAD_CREATION_CALLSTACK_PROPERTYNAME = "alma.acs.threadfactory.trace_creators";
/**
* Cache for value of {@link #LOG_THREAD_CREATION_CALLSTACK_PROPERTYNAME}.
*/
protected boolean logThreadCreationCallstack = Boolean.getBoolean(LOG_THREAD_CREATION_CALLSTACK_PROPERTYNAME);
/**
* We keep track of our threads also outside of the thread group.
*/
private final List<Thread> threadList = new ArrayList<Thread>();
/**
* Running index used for thread names.
*/
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final Logger logger;
private final String name;
private final String ownerName;
private LoggingThreadGroup group;
private ClassLoader newThreadContextCL;
/**
* Normal constructor.
* @param name the name of the {@link ThreadGroup} to which all threads created by this factory will belong.
* @param logger the logger to be used by this class
*/
public CleaningDaemonThreadFactory(String name, Logger logger) {
this(name, logger, "User");
}
/**
* Special constructor, e.g. when used internally by the container.
* @param name the name of the {@link ThreadGroup} to which all threads created by this factory will belong.
* @param logger the logger to be used by this class
* @param ownerName Can show up in log messages such as warnings by {@link LoggingThreadGroup#uncaughtException(Thread, Throwable)}.
*/
public CleaningDaemonThreadFactory(String name, Logger logger, String ownerName) {
this.logger = logger;
this.name = name;
this.ownerName = ownerName;
}
/**
* Creates a new daemon thread that is part of the same factory thread group
* as all other threads created by this method.
* The thread's name will be that of the group, with an integer value appended.
*
* @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
*/
public Thread newThread(Runnable command) {
ensureGroupCreated();
String threadName = group.getName() + "-" + threadNumber.getAndIncrement();
Thread t = new Thread(group, command, threadName);
t.setDaemon(true);
if (newThreadContextCL != null) {
t.setContextClassLoader(newThreadContextCL);
}
synchronized (threadList) {
threadList.add(t);
}
if (logThreadCreationCallstack) {
try {
throw new Exception();
} catch (Exception ex) {
StackTraceElement[] stack = ex.getStackTrace();
StringBuilder str = new StringBuilder();
for (int i = 1; i < stack.length; i++) { // skip 0-th element which represents this class itself
str.append(stack[i].toString());
if (i < stack.length - 1) {
str.append(" <- ");
}
}
String msg = "Created thread '" + threadName + "'. Call stack: " + str;
logger.log(AcsLogLevel.INFO, msg);
}
}
return t;
}
private synchronized void ensureGroupCreated() {
if (group == null) {
group = new LoggingThreadGroup(name, logger, ownerName);
group.setMaxPriority(Thread.currentThread().getPriority());
}
}
void setNewThreadContextClassLoader(ClassLoader cl) {
if (cl != null) {
newThreadContextCL = cl;
}
}
/**
* Gets a copy of the list of all threads created by this factory up to this call.
* This method should only be used for testing, but not in operational code.
*/
public List<Thread> _getAllThreadsCreated() {
synchronized (threadList) {
return new ArrayList<Thread>(threadList);
}
}
/**
* Kills running threads via {@link Thread#interrupt()} or {@link Thread#stop()}, see code comments about the two cases.
* Should be called by the container or similar classes when all threads
* created by this factory supposedly have terminated anyway thanks to smart applications.
* The safety concerns which led to the deprecation of the stop method thus don't seem to apply here.
*/
public synchronized void cleanUp() {
if (group == null) {
return;
}
group.setShuttingDown();
synchronized (threadList) {
for (Thread t : threadList) {
try {
if (t.isAlive()) {
logger.warning("Forcibly terminating surviving thread " + t.getName());
}
// @TODO HSO 2008-04: now that jbaci uses an external ThreadFactory, which is typically supplied by container services,
// we got jbaci test failures as long as stop() is called (see COMP-2362).
// We must check if we fix jbaci and go back to thread.stop, or stay with thread.interrupt.
t.interrupt();
// t.stop();
// The following sleep of 1 ms is a concession to unit testing of this class,
// so that log messages produced during the interrupt() call will appear in the order of the calls.
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
// no harm in operations, but unit tests may fail
}
} catch (RuntimeException e) {
logger.finer("exception while stopping thread '" + t.getName() + "': " + e.toString());
}
}
threadList.clear();
}
try {
// if there are threads in the group which have never started, destroy() will fail
if (!group.isDestroyed() && group.activeCount() == 0) {
group.destroy();
}
} catch (Exception e) {
logger.finer("unexpectedly failed to destroy thread group " + group.getName() + e.toString());
}
// in case this factory gets used again, we should build a new thread group
group = null;
}
private static class LoggingThreadGroup extends ThreadGroup
{
private final Logger logger;
private volatile boolean shuttingDown = false;
private final String ownerName;
LoggingThreadGroup(String name, Logger logger, String ownerName) {
super(name);
this.logger = logger;
this.ownerName = ownerName;
}
void setShuttingDown() {
shuttingDown = true;
}
/**
* Called by the JVM if any of the threads in this thread group terminates with an error.
* Logs a warning to the logger provided in the ctor.
* <p>
* The error <code>ThreadDeath</code> is even logged during expected thread lifetime,
* because user threads are not supposed to be terminated through the deprecated <code>stop()</code> method
* during their normal operation. <br>
* During shutdown, exceptions are not logged, because the killing of surviving user threads
* is logged already in the <code>cleanUp</code> method.
* <p>
* @TODO: since JDK 1.5 an {@link UncaughtExceptionHandler} can be attached to each thread,
* which may be an alternative to using a thread group for this purpose.
*
* @see java.lang.ThreadGroup#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
public void uncaughtException(Thread t, Throwable e) {
if (!shuttingDown) {
logger.log(Level.WARNING, ownerName + " thread '" + t.getName() + "' terminated with error ", e);
}
// ThreadDeath must move on to really let the thread die
if (e instanceof ThreadDeath) {
super.uncaughtException(t, e);
}
}
}
}