    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
    019    package org.apache.commons.exec;
    020
    021    import java.util.Enumeration;
    022    import java.util.Vector;
    023
    024    /**
    025     * Destroys all registered <code>Process</code>es when the VM exits.
    026     */
    027    public 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    }