001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018 019 package org.apache.commons.exec; 020 021 import java.util.Enumeration; 022 import java.util.Vector; 023 024 /** 025 * Destroys all registered <code>Process</code>es when the VM exits. 026 */ 027 public class ShutdownHookProcessDestroyer implements ProcessDestroyer, Runnable { 028 029 /** the list of currently running processes */ 030 private final Vector processes = new Vector(); 031 032 /** The thread registered at the JVM to execute the shutdown handler */ 033 private ProcessDestroyerImpl destroyProcessThread = null; 034 035 /** Whether or not this ProcessDestroyer has been registered as a shutdown hook */ 036 private boolean added = false; 037 038 /** 039 * Whether or not this ProcessDestroyer is currently running as shutdown hook 040 */ 041 private volatile boolean running = false; 042 043 private class ProcessDestroyerImpl extends Thread { 044 045 private boolean shouldDestroy = true; 046 047 public ProcessDestroyerImpl() { 048 super("ProcessDestroyer Shutdown Hook"); 049 } 050 051 public void run() { 052 if (shouldDestroy) { 053 ShutdownHookProcessDestroyer.this.run(); 054 } 055 } 056 057 public void setShouldDestroy(final boolean shouldDestroy) { 058 this.shouldDestroy = shouldDestroy; 059 } 060 } 061 062 /** 063 * Constructs a <code>ProcessDestroyer</code> and obtains 064 * <code>Runtime.addShutdownHook()</code> and 065 * <code>Runtime.removeShutdownHook()</code> through reflection. The 066 * ProcessDestroyer manages a list of processes to be destroyed when the VM 067 * exits. If a process is added when the list is empty, this 068 * <code>ProcessDestroyer</code> is registered as a shutdown hook. If 069 * removing a process results in an empty list, the 070 * <code>ProcessDestroyer</code> is removed as a shutdown hook. 071 */ 072 public ShutdownHookProcessDestroyer() { 073 } 074 075 /** 076 * Registers this <code>ProcessDestroyer</code> as a shutdown hook, uses 077 * reflection to ensure pre-JDK 1.3 compatibility. 078 */ 079 private void addShutdownHook() { 080 if (!running) { 081 destroyProcessThread = new ProcessDestroyerImpl(); 082 Runtime.getRuntime().addShutdownHook(destroyProcessThread); 083 added = true; 084 } 085 } 086 087 /** 088 * Removes this <code>ProcessDestroyer</code> as a shutdown hook, uses 089 * reflection to ensure pre-JDK 1.3 compatibility 090 */ 091 private void removeShutdownHook() { 092 if (added && !running) { 093 boolean removed = Runtime.getRuntime().removeShutdownHook( 094 destroyProcessThread); 095 if (!removed) { 096 System.err.println("Could not remove shutdown hook"); 097 } 098 /* 099 * start the hook thread, a unstarted thread may not be eligible for 100 * garbage collection Cf.: http://developer.java.sun.com/developer/ 101 * bugParade/bugs/4533087.html 102 */ 103 104 destroyProcessThread.setShouldDestroy(false); 105 destroyProcessThread.start(); 106 // this should return quickly, since it basically is a NO-OP. 107 try { 108 destroyProcessThread.join(20000); 109 } catch (InterruptedException ie) { 110 // the thread didn't die in time 111 // it should not kill any processes unexpectedly 112 } 113 destroyProcessThread = null; 114 added = false; 115 } 116 } 117 118 /** 119 * Returns whether or not the ProcessDestroyer is registered as as shutdown 120 * hook 121 * 122 * @return true if this is currently added as shutdown hook 123 */ 124 public boolean isAddedAsShutdownHook() { 125 return added; 126 } 127 128 /** 129 * Returns <code>true</code> if the specified <code>Process</code> was 130 * successfully added to the list of processes to destroy upon VM exit. 131 * 132 * @param process 133 * the process to add 134 * @return <code>true</code> if the specified <code>Process</code> was 135 * successfully added 136 */ 137 public boolean add(final Process process) { 138 synchronized (processes) { 139 // if this list is empty, register the shutdown hook 140 if (processes.size() == 0) { 141 addShutdownHook(); 142 } 143 processes.addElement(process); 144 return processes.contains(process); 145 } 146 } 147 148 /** 149 * Returns <code>true</code> if the specified <code>Process</code> was 150 * successfully removed from the list of processes to destroy upon VM exit. 151 * 152 * @param process 153 * the process to remove 154 * @return <code>true</code> if the specified <code>Process</code> was 155 * successfully removed 156 */ 157 public boolean remove(final Process process) { 158 synchronized (processes) { 159 boolean processRemoved = processes.removeElement(process); 160 if (processRemoved && processes.size() == 0) { 161 removeShutdownHook(); 162 } 163 return processRemoved; 164 } 165 } 166 167 /** 168 * Returns the number of registered processes. 169 * 170 * @return the number of register process 171 */ 172 public int size() { 173 return processes.size(); 174 } 175 176 /** 177 * Invoked by the VM when it is exiting. 178 */ 179 public void run() { 180 synchronized (processes) { 181 running = true; 182 Enumeration e = processes.elements(); 183 while (e.hasMoreElements()) { 184 Process process = (Process) e.nextElement(); 185 try { 186 process.destroy(); 187 } 188 catch (Throwable t) { 189 System.err.println("Unable to terminate process during process shutdown"); 190 } 191 } 192 } 193 } 194 }