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    }