diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 57c75d76c2..d516af35c2 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -25,6 +25,18 @@ + + + + ${shutdown.backstop.timeout} + + + + @@ -620,5 +632,5 @@ - + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index df639ee91a..cf83b9364a 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -54,6 +54,13 @@ index.tracking.disableInTransactionIndexing=false # Change the failure behaviour of the configuration checker system.bootstrap.config_check.strict=true +# +# How long should shutdown wait to complete normally before +# taking stronger action and calling System.exit() +# in ms, 10,000 is 10 seconds +# +shutdown.backstop.timeout=10000 + # Server Single User Mode # note: # only allow named user (note: if blank or not set then will allow all users) diff --git a/source/java/org/alfresco/repo/shutdown/ShutdownBackstop.java b/source/java/org/alfresco/repo/shutdown/ShutdownBackstop.java new file mode 100644 index 0000000000..5055bc2274 --- /dev/null +++ b/source/java/org/alfresco/repo/shutdown/ShutdownBackstop.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.shutdown; + +import org.alfresco.util.AbstractLifecycleBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * The Shutdown backstop is a spring bean that will ensure that shutdown completes + * within a given time. + * + * If alfresco is blocked - for example waiting for a remote resource or a long + * running actionbackstop will wait for a timeout and then call System.exit() + * + * @author mrogers + * + */ +public class ShutdownBackstop extends AbstractLifecycleBean +{ + /** + * How long to go before shutdown (in ms) + * Default 10 Seconds. + */ + private int timeout = 10000; + + protected final static Log log = LogFactory.getLog(ShutdownBackstop.class); + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getTimeout() { + return timeout; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // Do logging here for side effect of initialising log object. + if (log.isDebugEnabled()) + { + log.debug("Shutdown backstop onBootstrap"); + } + + // Nothing to do here + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + log.info("Shutdown backstop timer started"); + Thread selfDestructThread = new ShutdownBackstopThread(timeout); + selfDestructThread.start(); + } + + /** + * This is a dangerous class! It will kill the JVM after sleeping + * for timeout ms. + * + * It also dumps information to the system console prior to termination. + */ + private class ShutdownBackstopThread extends Thread + { + int timeout; + + public ShutdownBackstopThread(int timeout) + { + this.timeout = timeout; + this.setDaemon(true); + this.setName("Alfresco Shutdown Backstop Thread (Self Destruct)"); + } + + public void run() + { + try + { + sleep(timeout); + } + catch (InterruptedException e) + { + // nothing to do here + } + + log.error("Alfresco terminating via Shutdown Backstop"); + + try + { + // Try to dump the status of all threads + SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss"); + String timeStr = sf.format(new Date()); + File f = new File("alf-backstop-" + timeStr + ".dmp"); + f.createNewFile(); + BufferedWriter w = new BufferedWriter(new FileWriter(f)); + w.write("Alfresco Shutdown Backstop Dump, time:" + timeStr); + w.newLine(); + listAllThreads(w); + w.close(); + } + catch (Throwable t) + { + System.out.println(t.toString()); + t.printStackTrace(); + } + + log.error("Alfresco terminated"); + System.exit(1); + } + + private void printThreadInfo(Thread t, String indent, BufferedWriter w) throws IOException + { + if (t == null){ + return; + } + + w.write(indent + "Thread: " + t.getName() + " Priority: " + + t.getPriority() + (t.isDaemon() ? " Daemon" : "") + + (t.isAlive() ? "" : " Not Alive")); + w.newLine(); + } + + /** + * Display info about a thread group + * @throws IOException + */ + private void printGroupInfo(ThreadGroup g, String indent, BufferedWriter w) throws IOException + { + if (g == null) + { + return; + } + int numThreads = g.activeCount(); + int numGroups = g.activeGroupCount(); + Thread[] threads = new Thread[numThreads]; + ThreadGroup[] groups = new ThreadGroup[numGroups]; + + g.enumerate(threads, false); + g.enumerate(groups, false); + + w.append(indent + "Thread Group: " + g.getName() + + " Max Priority: " + g.getMaxPriority() + + (g.isDaemon() ? " Daemon" : "")); + w.newLine(); + + for (int i = 0; i < numThreads; i++) + { + printThreadInfo(threads[i], indent + " ", w); + } + for (int i = 0; i < numGroups; i++) + { + printGroupInfo(groups[i], indent + " ", w); + } + } + + /** + * Find the root thread group and list it recursively + * @throws IOException + */ + public void listAllThreads(BufferedWriter w) throws IOException + { + + ThreadGroup currentThreadGroup; + ThreadGroup rootThreadGroup; + ThreadGroup parent; + + // Get the current thread group + currentThreadGroup = Thread.currentThread().getThreadGroup(); + + // Now go find the root thread group + rootThreadGroup = currentThreadGroup; + parent = rootThreadGroup.getParent(); + while (parent != null) { + rootThreadGroup = parent; + parent = parent.getParent(); + } + printGroupInfo(rootThreadGroup, "", w); + } + } +}