/*
* Copyright (c) 2015 Hudson.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Hudson - initial API and implementation and/or initial documentation
*/
package hudson.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
/**
* TrackedExecutor is an executor that can be created my clients knowing only
* <code>TrackedExecutor.class.getName()</code>, e.g., by:
* <pre>
* Class.forName(executorClassName).newInstance()
* </pre>
* But all instances of TrackedExecutor and for each instance, all threads
* created by TrackedExecutor can be shut down when, e.g., Hudson is
* undeployed by its container.
* <p>Shutdown is aggressive. If a thread does not respond to <code>interrupt</code>
* within 1 ms., the deprecated <code>thread.stop()</code> is called. This
* can mean that sometimes thread will be stopped before it has a chance to
* respond to interrupt.
* @author Bob Foster
*/
public class TrackedExecutor implements Executor {
private static final Logger LOGGER = Logger.getLogger(TrackedExecutor.class.getName());
/** Default implementation - subclasses may override */
protected ThreadFactory factory = Executors.defaultThreadFactory();
/** Executor list. Must not be weak as executors may be used and forgotten. */
private static final List<TrackedExecutor> executors = new ArrayList<TrackedExecutor>();
/** Thread list. Must be weak or could hold threads in memory. */
private final List<WeakReference<Thread>> threads = new ArrayList<WeakReference<Thread>>();
/** True if executor has been shut down */
private boolean shutdown;
/**
* Must have a null constructor if it is to be created by
* <code>hudson.util.TrackedExecutor.class.newInstance()</code>,
* as, e.g., guice does.
*/
public TrackedExecutor() {
synchronized (executors) {
executors.add(this);
}
}
@Override
public void execute(Runnable command) {
String runnableClass = command.getClass().getName();
Thread thread = null;
synchronized (threads) {
if (shutdown) {
LOGGER.severe("Attempt to run "+runnableClass+" after shutdown ignored");
throw new IllegalStateException();
}
thread = factory.newThread(command);
thread.setName(runnableClass);
threads.add(new WeakReference<Thread>(thread));
}
thread.start();
}
/**
* Shutdown all threads started by any TrackedExecutor whose contextClassLoader
* is the same as (==) the argument. TODO not certain
* this precaution is necessary, as a TrackedExecutor should
* always be created by a different classloader in each instance of
* Hudson in a container; but it costs very little to test.
*
* @param contextClassLoader if not null, only threads with a matching
* contextClassLoader are stopped; otherwise, all threads are stopped
*/
public static void shutdownAll(ClassLoader contextClassLoader) {
synchronized (executors) {
for (Iterator<TrackedExecutor> it = executors.iterator(); it.hasNext();) {
TrackedExecutor executor = it.next();
executor.shutdown(contextClassLoader);
if (executor.threads.isEmpty()) {
it.remove();
}
}
}
}
/**
* Shutdown all threads started by this executor whose contextClassLoader
* is the same as (==) the argument.
* @param contextClassLoader only threads whose contextClassLoader is ==
* are stopped
*/
public void shutdown(ClassLoader contextClassLoader) {
synchronized (threads) {
shutdown = true;
for (Iterator<WeakReference<Thread>> it = threads.iterator(); it.hasNext(); ) {
Thread thread = it.next().get();
if (thread != null
&& (contextClassLoader == null
|| thread.getContextClassLoader() == contextClassLoader)) {
stopThread(thread);
it.remove();
}
}
}
}
/**
* Subclass may override if there is a faster or better way to stop threads
* for this executor.
* @param thread to stop
*/
protected void stopThread(Thread thread) {
thread.interrupt();
try { thread.join(1); } catch (InterruptedException ignore) { }
if (thread.isAlive()) {
thread.stop();
}
}
/**
* Subclass for threads that are not interruptable.
*/
public static class NonInterruptableExecutor extends TrackedExecutor {
@Override
public void stopThread(Thread thread) {
thread.stop();
}
}
}