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