1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.lang.reflect.InvocationTargetException;
  19. import java.lang.reflect.Method;
  20. import java.util.Enumeration;
  21. import java.util.Vector;
  22. /**
  23. * Destroys all registered <code>Process</code>es when the VM exits.
  24. *
  25. * @since Ant 1.5
  26. */
  27. class ProcessDestroyer implements Runnable {
  28. private Vector processes = new Vector();
  29. // methods to register and unregister shutdown hooks
  30. private Method addShutdownHookMethod;
  31. private Method removeShutdownHookMethod;
  32. private ProcessDestroyerImpl destroyProcessThread = null;
  33. // whether or not this ProcessDestroyer has been registered as a
  34. // shutdown hook
  35. private boolean added = false;
  36. // whether or not this ProcessDestroyer is currently running as
  37. // shutdown hook
  38. private boolean running = false;
  39. private class ProcessDestroyerImpl extends Thread {
  40. private boolean shouldDestroy = true;
  41. public ProcessDestroyerImpl() {
  42. super("ProcessDestroyer Shutdown Hook");
  43. }
  44. public void run() {
  45. if (shouldDestroy) {
  46. ProcessDestroyer.this.run();
  47. }
  48. }
  49. public void setShouldDestroy(boolean shouldDestroy) {
  50. this.shouldDestroy = shouldDestroy;
  51. }
  52. }
  53. /**
  54. * Constructs a <code>ProcessDestroyer</code> and obtains
  55. * <code>Runtime.addShutdownHook()</code> and
  56. * <code>Runtime.removeShutdownHook()</code> through reflection. The
  57. * ProcessDestroyer manages a list of processes to be destroyed when the
  58. * VM exits. If a process is added when the list is empty,
  59. * this <code>ProcessDestroyer</code> is registered as a shutdown hook. If
  60. * removing a process results in an empty list, the
  61. * <code>ProcessDestroyer</code> is removed as a shutdown hook.
  62. */
  63. public ProcessDestroyer() {
  64. try {
  65. // check to see if the shutdown hook methods exists
  66. // (support pre-JDK 1.3 VMs)
  67. Class[] paramTypes = {Thread.class};
  68. addShutdownHookMethod =
  69. Runtime.class.getMethod("addShutdownHook", paramTypes);
  70. removeShutdownHookMethod =
  71. Runtime.class.getMethod("removeShutdownHook", paramTypes);
  72. // wait to add shutdown hook as needed
  73. } catch (NoSuchMethodException e) {
  74. // it just won't be added as a shutdown hook... :(
  75. } catch (Exception e) {
  76. e.printStackTrace();
  77. }
  78. }
  79. /**
  80. * Registers this <code>ProcessDestroyer</code> as a shutdown hook,
  81. * uses reflection to ensure pre-JDK 1.3 compatibility.
  82. */
  83. private void addShutdownHook() {
  84. if (addShutdownHookMethod != null && !running) {
  85. destroyProcessThread = new ProcessDestroyerImpl();
  86. Object[] args = {destroyProcessThread};
  87. try {
  88. addShutdownHookMethod.invoke(Runtime.getRuntime(), args);
  89. added = true;
  90. } catch (IllegalAccessException e) {
  91. e.printStackTrace();
  92. } catch (InvocationTargetException e) {
  93. Throwable t = e.getTargetException();
  94. if (t != null && t.getClass() == IllegalStateException.class) {
  95. // shutdown already is in progress
  96. running = true;
  97. } else {
  98. e.printStackTrace();
  99. }
  100. }
  101. }
  102. }
  103. /**
  104. * Removes this <code>ProcessDestroyer</code> as a shutdown hook,
  105. * uses reflection to ensure pre-JDK 1.3 compatibility
  106. */
  107. private void removeShutdownHook() {
  108. if (removeShutdownHookMethod != null && added && !running) {
  109. Object[] args = {destroyProcessThread};
  110. try {
  111. Boolean removed =
  112. (Boolean) removeShutdownHookMethod.invoke(
  113. Runtime.getRuntime(),
  114. args);
  115. if (!removed.booleanValue()) {
  116. System.err.println("Could not remove shutdown hook");
  117. }
  118. } catch (IllegalAccessException e) {
  119. e.printStackTrace();
  120. } catch (InvocationTargetException e) {
  121. Throwable t = e.getTargetException();
  122. if (t != null && t.getClass() == IllegalStateException.class) {
  123. // shutdown already is in progress
  124. running = true;
  125. } else {
  126. e.printStackTrace();
  127. }
  128. }
  129. // start the hook thread, a unstarted thread may not be
  130. // eligible for garbage collection
  131. // Cf.: http://developer.java.sun.com/developer/bugParade/bugs/4533087.html
  132. destroyProcessThread.setShouldDestroy(false);
  133. destroyProcessThread.start();
  134. // this should return quickly, since it basically is a NO-OP.
  135. try {
  136. destroyProcessThread.join(20000);
  137. } catch (InterruptedException ie) {
  138. // the thread didn't die in time
  139. // it should not kill any processes unexpectedly
  140. }
  141. destroyProcessThread = null;
  142. added = false;
  143. }
  144. }
  145. /**
  146. * Returns whether or not the ProcessDestroyer is registered as
  147. * as shutdown hook
  148. * @return true if this is currently added as shutdown hook
  149. */
  150. public boolean isAddedAsShutdownHook() {
  151. return added;
  152. }
  153. /**
  154. * Returns <code>true</code> if the specified <code>Process</code> was
  155. * successfully added to the list of processes to destroy upon VM exit.
  156. *
  157. * @param process the process to add
  158. * @return <code>true</code> if the specified <code>Process</code> was
  159. * successfully added
  160. */
  161. public boolean add(Process process) {
  162. synchronized (processes) {
  163. // if this list is empty, register the shutdown hook
  164. if (processes.size() == 0) {
  165. addShutdownHook();
  166. }
  167. processes.addElement(process);
  168. return processes.contains(process);
  169. }
  170. }
  171. /**
  172. * Returns <code>true</code> if the specified <code>Process</code> was
  173. * successfully removed from the list of processes to destroy upon VM exit.
  174. *
  175. * @param process the process to remove
  176. * @return <code>true</code> if the specified <code>Process</code> was
  177. * successfully removed
  178. */
  179. public boolean remove(Process process) {
  180. synchronized (processes) {
  181. boolean processRemoved = processes.removeElement(process);
  182. if (processRemoved && processes.size() == 0) {
  183. removeShutdownHook();
  184. }
  185. return processRemoved;
  186. }
  187. }
  188. /**
  189. * Invoked by the VM when it is exiting.
  190. */
  191. public void run() {
  192. synchronized (processes) {
  193. running = true;
  194. Enumeration e = processes.elements();
  195. while (e.hasMoreElements()) {
  196. ((Process) e.nextElement()).destroy();
  197. }
  198. }
  199. }
  200. }