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}