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 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 public 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 /** 072 * Creates a new watchdog with a given timeout. 073 * 074 * @param timeout 075 * the timeout for the process in milliseconds. It must be 076 * greater than 0 or 'INFINITE_TIMEOUT' 077 */ 078 public ExecuteWatchdog(final long timeout) { 079 this.killedProcess = false; 080 this.watch = false; 081 this.hasWatchdog = (timeout != INFINITE_TIMEOUT); 082 if(this.hasWatchdog) { 083 this.watchdog = new Watchdog(timeout); 084 this.watchdog.addTimeoutObserver(this); 085 } 086 else { 087 this.watchdog = null; 088 } 089 } 090 091 /** 092 * Watches the given process and terminates it, if it runs for too long. All 093 * information from the previous run are reset. 094 * 095 * @param process 096 * the process to monitor. It cannot be <tt>null</tt> 097 * @throws IllegalStateException 098 * if a process is still being monitored. 099 */ 100 public synchronized void start(final Process process) { 101 if (process == null) { 102 throw new NullPointerException("process is null."); 103 } 104 if (this.process != null) { 105 throw new IllegalStateException("Already running."); 106 } 107 this.caught = null; 108 this.killedProcess = false; 109 this.watch = true; 110 this.process = process; 111 if(this.hasWatchdog) { 112 watchdog.start(); 113 } 114 } 115 116 /** 117 * Stops the watcher. It will notify all threads possibly waiting on this 118 * object. 119 */ 120 public synchronized void stop() { 121 if(hasWatchdog) { 122 watchdog.stop(); 123 } 124 watch = false; 125 process = null; 126 } 127 128 /** 129 * Destroys the running process manually. 130 */ 131 public synchronized void destroyProcess() { 132 this.timeoutOccured(null); 133 this.stop(); 134 } 135 136 /** 137 * Called after watchdog has finished. 138 */ 139 public synchronized void timeoutOccured(final Watchdog w) { 140 try { 141 try { 142 // We must check if the process was not stopped 143 // before being here 144 if(process != null) { 145 process.exitValue(); 146 } 147 } catch (IllegalThreadStateException itse) { 148 // the process is not terminated, if this is really 149 // a timeout and not a manual stop then destroy it. 150 if (watch) { 151 killedProcess = true; 152 process.destroy(); 153 } 154 } 155 } catch (Exception e) { 156 caught = e; 157 DebugUtils.handleException("Getting the exit value of the process failed", e); 158 } finally { 159 cleanUp(); 160 } 161 } 162 163 164 /** 165 * This method will rethrow the exception that was possibly caught during 166 * the run of the process. It will only remains valid once the process has 167 * been terminated either by 'error', timeout or manual intervention. 168 * Information will be discarded once a new process is ran. 169 * 170 * @throws Exception 171 * a wrapped exception over the one that was silently swallowed 172 * and stored during the process run. 173 */ 174 public synchronized void checkException() throws Exception { 175 if (caught != null) { 176 throw caught; 177 } 178 } 179 180 /** 181 * Indicates whether or not the watchdog is still monitoring the process. 182 * 183 * @return <tt>true</tt> if the process is still running, otherwise 184 * <tt>false</tt>. 185 */ 186 public synchronized boolean isWatching() { 187 return watch; 188 } 189 190 /** 191 * Indicates whether the last process run was killed. 192 * 193 * @return <tt>true</tt> if the process was killed 194 * <tt>false</tt>. 195 */ 196 public synchronized boolean killedProcess() { 197 return killedProcess; 198 } 199 200 /** 201 * reset the monitor flag and the process. 202 */ 203 protected synchronized void cleanUp() { 204 watch = false; 205 process = null; 206 } 207 }