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 019package org.apache.commons.exec; 020 021import java.util.Enumeration; 022import java.util.Vector; 023 024/** 025 * Destroys all registered {@code Process}es when the VM exits. 026 * 027 * @version $Id: ShutdownHookProcessDestroyer.java 1636056 2014-11-01 21:12:52Z ggregory $ 028 */ 029public class ShutdownHookProcessDestroyer implements ProcessDestroyer, Runnable { 030 031 /** the list of currently running processes */ 032 private final Vector<Process> processes = new Vector<Process>(); 033 034 /** The thread registered at the JVM to execute the shutdown handler */ 035 private ProcessDestroyerImpl destroyProcessThread = null; 036 037 /** Whether or not this ProcessDestroyer has been registered as a shutdown hook */ 038 private boolean added = false; 039 040 /** 041 * Whether or not this ProcessDestroyer is currently running as shutdown hook 042 */ 043 private volatile boolean running = false; 044 045 private class ProcessDestroyerImpl extends Thread { 046 047 private boolean shouldDestroy = true; 048 049 public ProcessDestroyerImpl() { 050 super("ProcessDestroyer Shutdown Hook"); 051 } 052 053 @Override 054 public void run() { 055 if (shouldDestroy) { 056 ShutdownHookProcessDestroyer.this.run(); 057 } 058 } 059 060 public void setShouldDestroy(final boolean shouldDestroy) { 061 this.shouldDestroy = shouldDestroy; 062 } 063 } 064 065 /** 066 * Constructs a {@code ProcessDestroyer} and obtains 067 * {@code Runtime.addShutdownHook()} and 068 * {@code Runtime.removeShutdownHook()} through reflection. The 069 * ProcessDestroyer manages a list of processes to be destroyed when the VM 070 * exits. If a process is added when the list is empty, this 071 * {@code ProcessDestroyer} is registered as a shutdown hook. If 072 * removing a process results in an empty list, the 073 * {@code ProcessDestroyer} is removed as a shutdown hook. 074 */ 075 public ShutdownHookProcessDestroyer() { 076 } 077 078 /** 079 * Registers this {@code ProcessDestroyer} as a shutdown hook, uses 080 * reflection to ensure pre-JDK 1.3 compatibility. 081 */ 082 private void addShutdownHook() { 083 if (!running) { 084 destroyProcessThread = new ProcessDestroyerImpl(); 085 Runtime.getRuntime().addShutdownHook(destroyProcessThread); 086 added = true; 087 } 088 } 089 090 /** 091 * Removes this {@code ProcessDestroyer} as a shutdown hook, uses 092 * reflection to ensure pre-JDK 1.3 compatibility 093 */ 094 private void removeShutdownHook() { 095 if (added && !running) { 096 final boolean removed = Runtime.getRuntime().removeShutdownHook( 097 destroyProcessThread); 098 if (!removed) { 099 System.err.println("Could not remove shutdown hook"); 100 } 101 /* 102 * start the hook thread, a unstarted thread may not be eligible for 103 * garbage collection Cf.: http://developer.java.sun.com/developer/ 104 * bugParade/bugs/4533087.html 105 */ 106 107 destroyProcessThread.setShouldDestroy(false); 108 destroyProcessThread.start(); 109 // this should return quickly, since it basically is a NO-OP. 110 try { 111 destroyProcessThread.join(20000); 112 } catch (final InterruptedException ie) { 113 // the thread didn't die in time 114 // it should not kill any processes unexpectedly 115 } 116 destroyProcessThread = null; 117 added = false; 118 } 119 } 120 121 /** 122 * Returns whether or not the ProcessDestroyer is registered as as shutdown 123 * hook 124 * 125 * @return true if this is currently added as shutdown hook 126 */ 127 public boolean isAddedAsShutdownHook() { 128 return added; 129 } 130 131 /** 132 * Returns {@code true} if the specified {@code Process} was 133 * successfully added to the list of processes to destroy upon VM exit. 134 * 135 * @param process 136 * the process to add 137 * @return {@code true} if the specified {@code Process} was 138 * successfully added 139 */ 140 public boolean add(final Process process) { 141 synchronized (processes) { 142 // if this list is empty, register the shutdown hook 143 if (processes.size() == 0) { 144 addShutdownHook(); 145 } 146 processes.addElement(process); 147 return processes.contains(process); 148 } 149 } 150 151 /** 152 * Returns {@code true} if the specified {@code Process} was 153 * successfully removed from the list of processes to destroy upon VM exit. 154 * 155 * @param process 156 * the process to remove 157 * @return {@code true} if the specified {@code Process} was 158 * successfully removed 159 */ 160 public boolean remove(final Process process) { 161 synchronized (processes) { 162 final boolean processRemoved = processes.removeElement(process); 163 if (processRemoved && processes.size() == 0) { 164 removeShutdownHook(); 165 } 166 return processRemoved; 167 } 168 } 169 170 /** 171 * Returns the number of registered processes. 172 * 173 * @return the number of register process 174 */ 175 public int size() { 176 return processes.size(); 177 } 178 179 /** 180 * Invoked by the VM when it is exiting. 181 */ 182 public void run() { 183 synchronized (processes) { 184 running = true; 185 final Enumeration<Process> e = processes.elements(); 186 while (e.hasMoreElements()) { 187 final Process process = e.nextElement(); 188 try { 189 process.destroy(); 190 } 191 catch (final Throwable t) { 192 System.err.println("Unable to terminate process during process shutdown"); 193 } 194 } 195 } 196 } 197}