
hudson.util.TrackedExecutor Maven / Gradle / Ivy
Show all versions of hudson-core Show documentation
/*
* 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
* TrackedExecutor.class.getName()
, e.g., by:
*
* Class.forName(executorClassName).newInstance()
*
* 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.
* Shutdown is aggressive. If a thread does not respond to interrupt
* within 1 ms., the deprecated thread.stop()
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 executors = new ArrayList();
/** Thread list. Must be weak or could hold threads in memory. */
private final List> threads = new ArrayList>();
/** True if executor has been shut down */
private boolean shutdown;
/**
* Must have a null constructor if it is to be created by
* hudson.util.TrackedExecutor.class.newInstance()
,
* 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.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 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> 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();
}
}
}