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</code>es when the VM exits.
026 */
027public 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}