/* * 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<String, String> threadsStillActive = new HashMap<String, String>(); // name, stack threadsStillActive.put("prime-the-pump", ""); while ((threadsStillActive.size() > 0) && (countdown-- > 0)) { threadsStillActive.clear(); List<Thread> 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<Thread> interruptAllThreads() { List<Thread> nonDaemonThreads = new ArrayList<Thread>(); 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 } } }