org.rhq.enterprise.agent.AgentShutdownHook Maven / Gradle / Ivy
The newest version!
/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.agent;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import mazz.i18n.Logger;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.agent.i18n.AgentI18NFactory;
import org.rhq.enterprise.agent.i18n.AgentI18NResourceKeys;
/**
* Provides a way to gracefully shutdown the agent. This can be used
* as a shutdown hook, and it can also be used to perform some shutdown
* functions, like attempting to interrupt hanging threads.
*
* @author John Mazzitelli
*/
public class AgentShutdownHook extends Thread {
private final Logger log = AgentI18NFactory.getLogger(AgentShutdownHook.class);
/**
* The agent that will be shutdown when the shutdown hook is triggered.
*/
private final AgentMain agent;
/**
* Constructor for {@link AgentShutdownHook} that retains the reference to the agent that will be shutdown when
* the shutdown hook is triggered.
*
* @param agent the agent to be shutdown when shutdown hook is triggered.
*/
public AgentShutdownHook(AgentMain agent) {
this.agent = agent;
}
/**
* This is executed when the VM is shutting down.
*/
@Override
public void run() {
showMessage(AgentI18NResourceKeys.EXIT_SHUTTING_DOWN);
try {
// set our timebomb to ensure the agent dies
spawnKillThread(1000L * 60 * 5);
agent.shutdown();
// try to interrupt all non-daemon threads so they die faster; but only try a fixed number of times
int threadsStillAlive = waitForNonDaemonThreads();
if (threadsStillAlive > 0) {
showMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREADS_STILL_ALIVE, threadsStillAlive);
}
} catch (Throwable t) {
String errors = ThrowableUtil.getAllMessages(t);
log.error(t, AgentI18NResourceKeys.EXIT_SHUTDOWN_ERROR, errors);
agent.getOut().println(agent.getI18NMsg().getMsg(AgentI18NResourceKeys.EXIT_SHUTDOWN_ERROR, errors));
}
showMessage(AgentI18NResourceKeys.EXIT_SHUTDOWN_COMPLETE);
return;
}
/**
* If you want the current agent to be dead this method will attept to wait for all
* other non-daemon threads to die before returning.
*
* We log messages if we can't wait for them all for whatever reason.
*
* Note that obviously this method will not wait for the calling thread
* that is currently running this method.
*
* @return the number of still active non-daemon threads that haven't died even after waiting
*/
public int waitForNonDaemonThreads() {
try {
int countdown = 10; // we don't want to do this forever, when this gets to 0, stop
Map threadsStillActive = new HashMap(); // name, stack
threadsStillActive.put("prime-the-pump", "");
while ((threadsStillActive.size() > 0) && (countdown-- > 0)) {
threadsStillActive.clear();
List threads = interruptAllThreads();
showMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREAD_WAIT, threads.size());
for (Thread thread : threads) {
try {
thread.join(10000L);
} catch (InterruptedException ie) {
} finally {
if (thread.isAlive()) {
Throwable t = new Throwable("Thread [" + thread.getName() + "]");
t.setStackTrace(thread.getStackTrace());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(baos));
threadsStillActive.put(thread.getName(), baos.toString());
logDebugMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREAD_IS_STILL_ACTIVE,
thread.getName(), baos.toString());
}
}
}
}
if (threadsStillActive.size() > 0) {
showMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREAD_NO_MORE_WAIT, threadsStillActive.size());
}
return threadsStillActive.size();
} catch (Throwable t) {
showMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREAD_CANNOT_WAIT, ThrowableUtil.getAllMessages(t));
return Thread.activeCount();
}
}
/**
* We need to make sure our VM can die quickly, so this method will send interrupts
* to most of the threads running in the VM to try to get them to hurry up and die.
*
* @return the non-daemon threads that were interrupted
*/
public List interruptAllThreads() {
List nonDaemonThreads = new ArrayList();
try {
String currentThreadName = Thread.currentThread().getName();
int threadCount = Thread.activeCount();
Thread[] threads = new Thread[threadCount + 50]; // give a little more in case more threads were added quickly
Thread.enumerate(threads);
for (Thread thread : threads) {
// do not interrupt or count:
// - threads with 0 stack elements (these are system threads that won't hold up the VM exit)
// - our current thread
// interrupt but do not count:
// - daemon threads
// - the agent input thread
if (thread != null) {
StackTraceElement[] threadStackTrace = thread.getStackTrace();
String threadName = thread.getName();
if (threadStackTrace != null && threadStackTrace.length > 0
&& !currentThreadName.equals(threadName)) {
if (!AgentMain.PROMPT_INPUT_THREAD_NAME.equals(threadName) && !thread.isDaemon()) {
nonDaemonThreads.add(thread);
}
thread.interrupt();
}
}
}
} catch (Exception e) {
showMessage(AgentI18NResourceKeys.SHUTDOWNHOOK_THREAD_CANNOT_INT, ThrowableUtil.getAllMessages(e));
}
return nonDaemonThreads;
}
/**
* If something goes wrong and the VM does not die on its own when we think it should,
* the thread created by this method will explicitly kill the VM.
* Calling this method sets a timebomb that will kill the VM after a timer runs out.
* There is no way to stop this timebomb short of performing 007-type heroics.
*
* @param doomsday the number of milliseconds that the VM has left to live; once this
* time expires, a kill thread will execute System.exit(0).
*/
public void spawnKillThread(final long doomsday) {
Runnable killRunnable = new Runnable() {
public void run() {
try {
Thread.sleep(doomsday);
} catch (Throwable t) {
} finally {
System.exit(0); // boom.
}
}
};
Thread killThread = new Thread(killRunnable, "RHQ Agent Kill Thread");
killThread.setDaemon(true); // don't let the VM hang around just for us, hopefully, this kill thread isn't needed
killThread.start();
}
/**
* Because this object is performing very important and serious things, we will
* both log the message and output it to the console, to give the user ample
* notification of what is going on.
*
* @param msg
* @param args
*/
private void showMessage(String msg, Object... args) {
try {
log.info(msg, args);
agent.getOut().println(this.agent.getI18NMsg().getMsg(msg, args));
} catch (Throwable t) {
// do not allow exceptions that occur in here to stop our shutdown
}
}
/**
* Logs a debug message - it ignores exceptions but won't output anything to the console.
*
* @param msg
* @param args
*/
private void logDebugMessage(String msg, Object... args) {
try {
log.debug(msg, args);
} catch (Throwable t) {
// do not allow exceptions that occur in here to stop our shutdown
}
}
}