PageRenderTime 33ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/DefaultExecHandle.java

#
Java | 357 lines | 233 code | 48 blank | 76 comment | 19 complexity | a9fab8f3069516165ef72b5215b5ffdd MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright 2009 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.gradle.util.exec;
  17. import org.gradle.util.ThreadUtils;
  18. import org.gradle.util.shutdown.ShutdownHookActionRegister;
  19. import java.io.File;
  20. import java.util.*;
  21. import java.util.concurrent.locks.Lock;
  22. import java.util.concurrent.locks.ReentrantLock;
  23. import java.util.concurrent.locks.Condition;
  24. import java.util.concurrent.CopyOnWriteArrayList;
  25. import java.util.concurrent.Executors;
  26. import java.util.concurrent.ExecutorService;
  27. /**
  28. * Default implementation for the ExecHandle interface.
  29. *
  30. * <h3>State flows</h3>
  31. *
  32. * <p>The ExecHandle has very strict state control.
  33. * The following state flows are allowed:</p>
  34. *
  35. * Normal state flow:
  36. * <ul><li>INIT -> STARTED -> SUCCEEDED</li></ul>
  37. * Failure state flows:
  38. * <ul>
  39. * <li>INIT -> FAILED</li>
  40. * <li>INIT -> STARTED -> FAILED</li>
  41. * </ul>
  42. * Aborted state flow:
  43. * <ul><li>INIT -> STARTED -> ABORTED</li></ul>
  44. *
  45. * State is controlled on all control methods:
  46. * <ul>
  47. * <li>{@link #start()} can only be called when the state is NOT {@link ExecHandleState#STARTED}</li>
  48. * <li>{@link #abort()} can only be called when the state is {@link ExecHandleState#STARTED}</li>
  49. * <li>{@link #startAndWaitForFinish()} can only be called when the state is NOT {@link ExecHandleState#STARTED}</li>
  50. * </ul>
  51. *
  52. * @author Tom Eyckmans
  53. */
  54. public class DefaultExecHandle implements ExecHandle {
  55. /**
  56. * The working directory of the process.
  57. */
  58. private final File directory;
  59. /**
  60. * The executable to run.
  61. */
  62. private final String command;
  63. /**
  64. * Arguments to pass to the executable.
  65. */
  66. private final List<String> arguments;
  67. /**
  68. * The exit code of the executable when it terminates normally.
  69. */
  70. private final int normalTerminationExitCode;
  71. /**
  72. * The variables to set in the environment the executable is run in.
  73. */
  74. private final Map<String, String> environment;
  75. /**
  76. * Time in ms to sleep the 'main' Thread that is waiting for the external process to be terminated. Note that this
  77. * timeout is only used when the {@link Process#waitFor} method is interrupted so it's use very limited.
  78. */
  79. private final long keepWaitingTimeout;
  80. /**
  81. * The output handle to pass the standard output of the external process to.
  82. */
  83. private final ExecOutputHandle standardOutputHandle;
  84. /**
  85. * The output handle to pass the error output of the external process to.
  86. */
  87. private final ExecOutputHandle errorOutputHandle;
  88. /**
  89. * Lock to guard all mutable state
  90. */
  91. private final Lock lock;
  92. private final Condition stateChange;
  93. /**
  94. * State of this ExecHandle.
  95. */
  96. private ExecHandleState state;
  97. /**
  98. * When not null, the runnable that is waiting
  99. */
  100. private ExecHandleRunner execHandleRunner;
  101. private ExecutorService threadPool;
  102. private int exitCode;
  103. private Throwable failureCause;
  104. private final ExecHandleNotifierFactory notifierFactory;
  105. private final List<ExecHandleListener> listeners = new CopyOnWriteArrayList<ExecHandleListener>();
  106. private ExecHandleShutdownHookAction shutdownHookAction;
  107. DefaultExecHandle(File directory, String command, List<?> arguments, int normalTerminationExitCode,
  108. Map<String, String> environment, long keepWaitingTimeout, ExecOutputHandle standardOutputHandle,
  109. ExecOutputHandle errorOutputHandle, ExecHandleNotifierFactory notifierFactory,
  110. List<ExecHandleListener> listeners) {
  111. this.directory = directory;
  112. this.command = command;
  113. this.arguments = new ArrayList<String>();
  114. for (Object objectArgument : arguments) { // to handle GStrings! otherwise ClassCassExceptions may occur.
  115. if (objectArgument != null) {
  116. this.arguments.add(objectArgument.toString());
  117. }
  118. }
  119. this.normalTerminationExitCode = normalTerminationExitCode;
  120. this.environment = environment;
  121. this.keepWaitingTimeout = keepWaitingTimeout;
  122. this.standardOutputHandle = standardOutputHandle;
  123. this.errorOutputHandle = errorOutputHandle;
  124. this.lock = new ReentrantLock();
  125. this.stateChange = lock.newCondition();
  126. this.state = ExecHandleState.INIT;
  127. this.notifierFactory = notifierFactory;
  128. if (listeners != null && !listeners.isEmpty()) {
  129. this.listeners.addAll(listeners);
  130. }
  131. }
  132. public File getDirectory() {
  133. return directory;
  134. }
  135. public String getCommand() {
  136. return command;
  137. }
  138. public List<String> getArguments() {
  139. return Collections.unmodifiableList(arguments);
  140. }
  141. public Map<String, String> getEnvironment() {
  142. return Collections.unmodifiableMap(environment);
  143. }
  144. public long getKeepWaitingTimeout() {
  145. return keepWaitingTimeout;
  146. }
  147. public ExecOutputHandle getStandardOutputHandle() {
  148. return standardOutputHandle;
  149. }
  150. public ExecOutputHandle getErrorOutputHandle() {
  151. return errorOutputHandle;
  152. }
  153. public ExecHandleState getState() {
  154. lock.lock();
  155. try {
  156. return state;
  157. } finally {
  158. lock.unlock();
  159. }
  160. }
  161. private void setState(ExecHandleState state) {
  162. lock.lock();
  163. try {
  164. this.state = state;
  165. stateChange.signalAll();
  166. } finally {
  167. lock.unlock();
  168. }
  169. }
  170. private boolean stateIn(ExecHandleState... states) {
  171. lock.lock();
  172. try {
  173. return Arrays.asList(states).contains(this.state);
  174. } finally {
  175. lock.unlock();
  176. }
  177. }
  178. public int getNormalTerminationExitCode() {
  179. return normalTerminationExitCode;
  180. }
  181. public int getExitCode() {
  182. lock.lock();
  183. try {
  184. if (!stateIn(ExecHandleState.SUCCEEDED, ExecHandleState.FAILED)) {
  185. throw new IllegalStateException("not in succeeded or failed state!");
  186. }
  187. return exitCode;
  188. } finally {
  189. lock.unlock();
  190. }
  191. }
  192. public Throwable getFailureCause() {
  193. lock.lock();
  194. try {
  195. if (!stateIn(ExecHandleState.FAILED)) {
  196. throw new IllegalStateException("not in failed state!");
  197. }
  198. return failureCause;
  199. } finally {
  200. lock.unlock();
  201. }
  202. }
  203. private void setEndStateInfo(ExecHandleState state, int exitCode, Throwable failureCause) {
  204. lock.lock();
  205. try {
  206. setState(state);
  207. this.exitCode = exitCode;
  208. this.failureCause = failureCause;
  209. } finally {
  210. lock.unlock();
  211. }
  212. }
  213. public void start() {
  214. lock.lock();
  215. try {
  216. if (!stateIn(ExecHandleState.INIT)) {
  217. throw new IllegalStateException("already started!");
  218. }
  219. setState(ExecHandleState.STARTING);
  220. exitCode = -1;
  221. failureCause = null;
  222. threadPool = Executors.newFixedThreadPool(3);
  223. execHandleRunner = new ExecHandleRunner(this, threadPool);
  224. threadPool.execute(execHandleRunner);
  225. while (getState() == ExecHandleState.STARTING) {
  226. try {
  227. stateChange.await();
  228. } catch (InterruptedException e) {
  229. throw new RuntimeException(e);
  230. }
  231. }
  232. } finally {
  233. lock.unlock();
  234. }
  235. }
  236. public void abort() {
  237. lock.lock();
  238. try {
  239. if (!stateIn(ExecHandleState.STARTED)) {
  240. throw new IllegalStateException("not in started state!");
  241. }
  242. this.execHandleRunner.stopWaiting();
  243. } finally {
  244. lock.unlock();
  245. }
  246. }
  247. public ExecHandleState waitForFinish() {
  248. ThreadUtils.awaitTermination(threadPool);
  249. return getState();
  250. }
  251. private void shutdownThreadPool() {
  252. ThreadUtils.run(new Runnable() {
  253. public void run() {
  254. ThreadUtils.shutdown(threadPool);
  255. }
  256. });
  257. }
  258. public ExecHandleState startAndWaitForFinish() {
  259. start();
  260. waitForFinish();
  261. return getState();
  262. }
  263. void started() {
  264. shutdownHookAction = ExecHandleShutdownHookAction.forHandle(this);
  265. ShutdownHookActionRegister.addShutdownHookAction(shutdownHookAction);
  266. setState(ExecHandleState.STARTED);
  267. ThreadUtils.run(notifierFactory.createStartedNotifier(this));
  268. }
  269. void finished(int exitCode) {
  270. ShutdownHookActionRegister.removeShutdownHookAction(shutdownHookAction);
  271. if (exitCode != normalTerminationExitCode) {
  272. setEndStateInfo(ExecHandleState.FAILED, exitCode, new RuntimeException(
  273. "exitCode(" + exitCode + ") != " + normalTerminationExitCode + "!"));
  274. shutdownThreadPool();
  275. ThreadUtils.run(notifierFactory.createFailedNotifier(this));
  276. } else {
  277. setEndStateInfo(ExecHandleState.SUCCEEDED, 0, null);
  278. shutdownThreadPool();
  279. ThreadUtils.run(notifierFactory.createSucceededNotifier(this));
  280. }
  281. }
  282. void aborted() {
  283. ShutdownHookActionRegister.removeShutdownHookAction(shutdownHookAction);
  284. setState(ExecHandleState.ABORTED);
  285. shutdownThreadPool();
  286. ThreadUtils.run(notifierFactory.createAbortedNotifier(this));
  287. }
  288. void failed(Throwable failureCause) {
  289. ShutdownHookActionRegister.removeShutdownHookAction(shutdownHookAction);
  290. setEndStateInfo(ExecHandleState.FAILED, -1, failureCause);
  291. shutdownThreadPool();
  292. ThreadUtils.run(notifierFactory.createFailedNotifier(this));
  293. }
  294. public void addListeners(ExecHandleListener... listeners) {
  295. if (listeners == null) {
  296. throw new IllegalArgumentException("listeners == null!");
  297. }
  298. this.listeners.addAll(Arrays.asList(listeners));
  299. }
  300. public void removeListeners(ExecHandleListener... listeners) {
  301. if (listeners == null) {
  302. throw new IllegalArgumentException("listeners == null!");
  303. }
  304. this.listeners.removeAll(Arrays.asList(listeners));
  305. }
  306. public List<ExecHandleListener> getListeners() {
  307. return Collections.unmodifiableList(listeners);
  308. }
  309. }