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 }