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