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 org.apache.commons.exec.util.DebugUtils; 022 023/** 024 * Destroys a process running for too long. For example: 025 * 026 * <pre> 027 * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000); 028 * Executer exec = new Executer(myloghandler, watchdog); 029 * exec.setCommandLine(mycmdline); 030 * int exitvalue = exec.execute(); 031 * if (Execute.isFailure(exitvalue) && watchdog.killedProcess()) { 032 * // it was killed on purpose by the watchdog 033 * } 034 * </pre> 035 * 036 * When starting an asynchronous process than 'ExecuteWatchdog' is the 037 * keeper of the process handle. In some cases it is useful not to define 038 * a timeout (and pass 'INFINITE_TIMEOUT') and to kill the process explicitly 039 * using 'destroyProcess()'. 040 * <p> 041 * Please note that ExecuteWatchdog is processed asynchronously, e.g. it might 042 * be still attached to a process even after the DefaultExecutor.execute 043 * has returned. 044 * 045 * @see org.apache.commons.exec.Executor 046 * @see org.apache.commons.exec.Watchdog 047 */ 048public class ExecuteWatchdog implements TimeoutObserver { 049 050 /** The marker for an infinite timeout */ 051 public static final long INFINITE_TIMEOUT = -1; 052 053 /** The process to execute and watch for duration. */ 054 private Process process; 055 056 /** Is a user-supplied timeout in use */ 057 private final boolean hasWatchdog; 058 059 /** Say whether or not the watchdog is currently monitoring a process. */ 060 private boolean watch; 061 062 /** Exception that might be thrown during the process execution. */ 063 private Exception caught; 064 065 /** Say whether or not the process was killed due to running overtime. */ 066 private boolean killedProcess; 067 068 /** Will tell us whether timeout has occurred. */ 069 private final Watchdog watchdog; 070 071 /** Indicates that the process is verified as started */ 072 private volatile boolean processStarted; 073 074 /** 075 * Creates a new watchdog with a given timeout. 076 * 077 * @param timeout 078 * the timeout for the process in milliseconds. It must be 079 * greater than 0 or 'INFINITE_TIMEOUT' 080 */ 081 public ExecuteWatchdog(final long timeout) { 082 this.killedProcess = false; 083 this.watch = false; 084 this.hasWatchdog = (timeout != INFINITE_TIMEOUT); 085 this.processStarted = false; 086 if(this.hasWatchdog) { 087 this.watchdog = new Watchdog(timeout); 088 this.watchdog.addTimeoutObserver(this); 089 } 090 else { 091 this.watchdog = null; 092 } 093 } 094 095 /** 096 * Watches the given process and terminates it, if it runs for too long. All 097 * information from the previous run are reset. 098 * 099 * @param process 100 * the process to monitor. It cannot be <tt>null</tt> 101 * @throws IllegalStateException 102 * if a process is still being monitored. 103 */ 104 public synchronized void start(final Process process) { 105 if (process == null) { 106 throw new NullPointerException("process is null."); 107 } 108 if (this.process != null) { 109 throw new IllegalStateException("Already running."); 110 } 111 this.caught = null; 112 this.killedProcess = false; 113 this.watch = true; 114 this.process = process; 115 this.processStarted = true; 116 this.notifyAll(); 117 if(this.hasWatchdog) { 118 watchdog.start(); 119 } 120 } 121 122 /** 123 * Stops the watcher. It will notify all threads possibly waiting on this 124 * object. 125 */ 126 public synchronized void stop() { 127 if(hasWatchdog) { 128 watchdog.stop(); 129 } 130 watch = false; 131 process = null; 132 } 133 134 /** 135 * Destroys the running process manually. 136 */ 137 public synchronized void destroyProcess() { 138 ensureStarted(); 139 this.timeoutOccured(null); 140 this.stop(); 141 } 142 143 /** 144 * Called after watchdog has finished. 145 */ 146 public synchronized void timeoutOccured(final Watchdog w) { 147 try { 148 try { 149 // We must check if the process was not stopped 150 // before being here 151 if(process != null) { 152 process.exitValue(); 153 } 154 } catch (IllegalThreadStateException itse) { 155 // the process is not terminated, if this is really 156 // a timeout and not a manual stop then destroy it. 157 if (watch) { 158 killedProcess = true; 159 process.destroy(); 160 } 161 } 162 } catch (Exception e) { 163 caught = e; 164 DebugUtils.handleException("Getting the exit value of the process failed", e); 165 } finally { 166 cleanUp(); 167 } 168 } 169 170 171 /** 172 * This method will rethrow the exception that was possibly caught during 173 * the run of the process. It will only remains valid once the process has 174 * been terminated either by 'error', timeout or manual intervention. 175 * Information will be discarded once a new process is ran. 176 * 177 * @throws Exception 178 * a wrapped exception over the one that was silently swallowed 179 * and stored during the process run. 180 */ 181 public synchronized void checkException() throws Exception { 182 if (caught != null) { 183 throw caught; 184 } 185 } 186 187 /** 188 * Indicates whether or not the watchdog is still monitoring the process. 189 * 190 * @return <tt>true</tt> if the process is still running, otherwise 191 * <tt>false</tt>. 192 */ 193 public synchronized boolean isWatching() { 194 ensureStarted(); 195 return watch; 196 } 197 198 /** 199 * Indicates whether the last process run was killed. 200 * 201 * @return <tt>true</tt> if the process was killed 202 * <tt>false</tt>. 203 */ 204 public synchronized boolean killedProcess() { 205 return killedProcess; 206 } 207 208 /** 209 * reset the monitor flag and the process. 210 */ 211 protected synchronized void cleanUp() { 212 watch = false; 213 process = null; 214 } 215 216 void setProcessNotStarted(){ 217 processStarted = false; 218 } 219 220 /** 221 * Ensures that the process is started, so we do not race with asynch execution. 222 * The caller of this method must be holding the lock on this 223 */ 224 private void ensureStarted(){ 225 while (!processStarted){ 226 try { 227 this.wait(); 228 } catch (InterruptedException e) { 229 throw new RuntimeException(e.getMessage()); 230 } 231 } 232 } 233}