PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/org/apache/hadoop/util/Shell.java

https://github.com/dbogdanovich/hadoop2-windows-patch
Java | 726 lines | 424 code | 91 blank | 211 comment | 48 complexity | fdb5483acf45184b6a80f8b772207e3e MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.hadoop.util;
  19. import java.io.BufferedReader;
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.io.InputStreamReader;
  23. import java.util.Arrays;
  24. import java.util.Map;
  25. import java.util.Timer;
  26. import java.util.TimerTask;
  27. import java.util.concurrent.atomic.AtomicBoolean;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. /**
  31. * A base class for running a Unix command.
  32. *
  33. * <code>Shell</code> can be used to run unix commands like <code>du</code> or
  34. * <code>df</code>. It also offers facilities to gate commands by
  35. * time-intervals.
  36. */
  37. abstract public class Shell {
  38. public static final Log LOG = LogFactory.getLog(Shell.class);
  39. private static boolean IS_JAVA7_OR_ABOVE =
  40. System.getProperty("java.version").substring(0, 3).compareTo("1.7") >= 0;
  41. public static boolean isJava7OrAbove() {
  42. return IS_JAVA7_OR_ABOVE;
  43. }
  44. /** a Unix command to get the current user's name */
  45. public final static String USER_NAME_COMMAND = "whoami";
  46. /** Windows CreateProcess synchronization object */
  47. public static final Object WindowsProcessLaunchLock = new Object();
  48. /** a Unix command to get the current user's groups list */
  49. public static String[] getGroupsCommand() {
  50. return (WINDOWS)? new String[]{"cmd", "/c", "groups"}
  51. : new String[]{"bash", "-c", "groups"};
  52. }
  53. /** a Unix command to get a given user's groups list */
  54. public static String[] getGroupsForUserCommand(final String user) {
  55. //'groups username' command return is non-consistent across different unixes
  56. return (WINDOWS)? new String[] { WINUTILS, "groups", "-F", "\"" + user + "\""}
  57. : new String [] {"bash", "-c", "id -Gn " + user};
  58. }
  59. /** a Unix command to get a given netgroup's user list */
  60. public static String[] getUsersForNetgroupCommand(final String netgroup) {
  61. //'groups username' command return is non-consistent across different unixes
  62. return (WINDOWS)? new String [] {"cmd", "/c", "getent netgroup " + netgroup}
  63. : new String [] {"bash", "-c", "getent netgroup " + netgroup};
  64. }
  65. /** Return a command to get permission information. */
  66. public static String[] getGetPermissionCommand() {
  67. return (WINDOWS) ? new String[] { WINUTILS, "ls", "-F" }
  68. : new String[] { "/bin/ls", "-ld" };
  69. }
  70. /** Return a command to set permission */
  71. public static String[] getSetPermissionCommand(String perm, boolean recursive) {
  72. if (recursive) {
  73. return (WINDOWS) ? new String[] { WINUTILS, "chmod", "-R", perm }
  74. : new String[] { "chmod", "-R", perm };
  75. } else {
  76. return (WINDOWS) ? new String[] { WINUTILS, "chmod", perm }
  77. : new String[] { "chmod", perm };
  78. }
  79. }
  80. /**
  81. * Return a command to set permission for specific file.
  82. *
  83. * @param perm String permission to set
  84. * @param recursive boolean true to apply to all sub-directories recursively
  85. * @param file String file to set
  86. * @return String[] containing command and arguments
  87. */
  88. public static String[] getSetPermissionCommand(String perm, boolean recursive,
  89. String file) {
  90. String[] baseCmd = getSetPermissionCommand(perm, recursive);
  91. String[] cmdWithFile = Arrays.copyOf(baseCmd, baseCmd.length + 1);
  92. cmdWithFile[cmdWithFile.length - 1] = file;
  93. return cmdWithFile;
  94. }
  95. /** Return a command to set owner */
  96. public static String[] getSetOwnerCommand(String owner) {
  97. return (WINDOWS) ? new String[] { WINUTILS, "chown", "\"" + owner + "\"" }
  98. : new String[] { "chown", owner };
  99. }
  100. /** Return a command to create symbolic links */
  101. public static String[] getSymlinkCommand(String target, String link) {
  102. return WINDOWS ? new String[] { WINUTILS, "symlink", link, target }
  103. : new String[] { "ln", "-s", target, link };
  104. }
  105. /** Return a command to read the target of the a symbolic link*/
  106. public static String[] getReadlinkCommand(String link) {
  107. return WINDOWS ? new String[] { WINUTILS, "readlink", link }
  108. : new String[] { "readlink", link };
  109. }
  110. /** Return a command for determining if process with specified pid is alive. */
  111. public static String[] getCheckProcessIsAliveCommand(String pid) {
  112. return Shell.WINDOWS ?
  113. new String[] { Shell.WINUTILS, "task", "isAlive", pid } :
  114. new String[] { "kill", "-0", isSetsidAvailable ? "-" + pid : pid };
  115. }
  116. /** Return a command to send a signal to a given pid */
  117. public static String[] getSignalKillCommand(int code, String pid) {
  118. return Shell.WINDOWS ? new String[] { Shell.WINUTILS, "task", "kill", pid } :
  119. new String[] { "kill", "-" + code, isSetsidAvailable ? "-" + pid : pid };
  120. }
  121. /** Return a regular expression string that match environment variables */
  122. public static String getEnvironmentVariableRegex() {
  123. return (WINDOWS) ? "%([A-Za-z_][A-Za-z0-9_]*?)%" :
  124. "\\$([A-Za-z_][A-Za-z0-9_]*)";
  125. }
  126. /**
  127. * Returns a File referencing a script with the given basename, inside the
  128. * given parent directory. The file extension is inferred by platform: ".cmd"
  129. * on Windows, or ".sh" otherwise.
  130. *
  131. * @param parent File parent directory
  132. * @param basename String script file basename
  133. * @return File referencing the script in the directory
  134. */
  135. public static File appendScriptExtension(File parent, String basename) {
  136. return new File(parent, appendScriptExtension(basename));
  137. }
  138. /**
  139. * Returns a script file name with the given basename. The file extension is
  140. * inferred by platform: ".cmd" on Windows, or ".sh" otherwise.
  141. *
  142. * @param basename String script file basename
  143. * @return String script file name
  144. */
  145. public static String appendScriptExtension(String basename) {
  146. return basename + (WINDOWS ? ".cmd" : ".sh");
  147. }
  148. /**
  149. * Returns a command to run the given script. The script interpreter is
  150. * inferred by platform: cmd on Windows or bash otherwise.
  151. *
  152. * @param script File script to run
  153. * @return String[] command to run the script
  154. */
  155. public static String[] getRunScriptCommand(File script) {
  156. String absolutePath = script.getAbsolutePath();
  157. return WINDOWS ? new String[] { "cmd", "/c", absolutePath } :
  158. new String[] { "/bin/bash", absolutePath };
  159. }
  160. /** a Unix command to set permission */
  161. public static final String SET_PERMISSION_COMMAND = "chmod";
  162. /** a Unix command to set owner */
  163. public static final String SET_OWNER_COMMAND = "chown";
  164. /** a Unix command to set the change user's groups list */
  165. public static final String SET_GROUP_COMMAND = "chgrp";
  166. /** a Unix command to create a link */
  167. public static final String LINK_COMMAND = "ln";
  168. /** a Unix command to get a link target */
  169. public static final String READ_LINK_COMMAND = "readlink";
  170. /**Time after which the executing script would be timedout*/
  171. protected long timeOutInterval = 0L;
  172. /** If or not script timed out*/
  173. private AtomicBoolean timedOut;
  174. /** Centralized logic to discover and validate the sanity of the Hadoop
  175. * home directory. Returns either NULL or a directory that exists and
  176. * was specified via either -Dhadoop.home.dir or the HADOOP_HOME ENV
  177. * variable. This does a lot of work so it should only be called
  178. * privately for initialization once per process.
  179. **/
  180. private static String checkHadoopHome() {
  181. // first check the Dflag hadoop.home.dir with JVM scope
  182. String home = System.getProperty("hadoop.home.dir");
  183. // fall back to the system/user-global env variable
  184. if (home == null) {
  185. home = System.getenv("HADOOP_HOME");
  186. }
  187. try {
  188. // couldn't find either setting for hadoop's home directory
  189. if (home == null) {
  190. throw new IOException("HADOOP_HOME or hadoop.home.dir are not set.");
  191. }
  192. if (home.startsWith("\"") && home.endsWith("\"")) {
  193. home = home.substring(1, home.length()-1);
  194. }
  195. // check that the home setting is actually a directory that exists
  196. File homedir = new File(home);
  197. if (!homedir.isAbsolute() || !homedir.exists() || !homedir.isDirectory()) {
  198. throw new IOException("Hadoop home directory " + homedir
  199. + " does not exist, is not a directory, or is not an absolute path.");
  200. }
  201. home = homedir.getCanonicalPath();
  202. } catch (IOException ioe) {
  203. if (LOG.isDebugEnabled()) {
  204. LOG.debug("Failed to detect a valid hadoop home directory", ioe);
  205. }
  206. home = null;
  207. }
  208. return home;
  209. }
  210. private static String HADOOP_HOME_DIR = checkHadoopHome();
  211. // Public getter, throws an exception if HADOOP_HOME failed validation
  212. // checks and is being referenced downstream.
  213. public static final String getHadoopHome() throws IOException {
  214. if (HADOOP_HOME_DIR == null) {
  215. throw new IOException("Misconfigured HADOOP_HOME cannot be referenced.");
  216. }
  217. return HADOOP_HOME_DIR;
  218. }
  219. /** fully qualify the path to a binary that should be in a known hadoop
  220. * bin location. This is primarily useful for disambiguating call-outs
  221. * to executable sub-components of Hadoop to avoid clashes with other
  222. * executables that may be in the path. Caveat: this call doesn't
  223. * just format the path to the bin directory. It also checks for file
  224. * existence of the composed path. The output of this call should be
  225. * cached by callers.
  226. * */
  227. public static final String getQualifiedBinPath(String executable)
  228. throws IOException {
  229. // construct hadoop bin path to the specified executable
  230. String fullExeName = HADOOP_HOME_DIR + File.separator + "bin"
  231. + File.separator + executable;
  232. File exeFile = new File(fullExeName);
  233. if (!exeFile.exists()) {
  234. throw new IOException("Could not locate executable " + fullExeName
  235. + " in the Hadoop binaries.");
  236. }
  237. return exeFile.getCanonicalPath();
  238. }
  239. /** Set to true on Windows platforms */
  240. public static final boolean WINDOWS /* borrowed from Path.WINDOWS */
  241. = System.getProperty("hadoop.os.name", "Linux").startsWith("Windows");
  242. public static final boolean LINUX
  243. = System.getProperty("hadoop.os.name", "Linux").startsWith("Linux");
  244. /** a Windows utility to emulate Unix commands */
  245. public static final String WINUTILS = getWinUtilsPath();
  246. public static final String getWinUtilsPath() {
  247. String winUtilsPath = null;
  248. try {
  249. if (WINDOWS) {
  250. winUtilsPath = getQualifiedBinPath("winutils.exe");
  251. }
  252. } catch (IOException ioe) {
  253. LOG.error("Failed to locate the winutils binary in the hadoop binary path",
  254. ioe);
  255. }
  256. return winUtilsPath;
  257. }
  258. public static final boolean isSetsidAvailable = isSetsidSupported();
  259. private static boolean isSetsidSupported() {
  260. if (Shell.WINDOWS) {
  261. return false;
  262. }
  263. ShellCommandExecutor shexec = null;
  264. boolean setsidSupported = true;
  265. try {
  266. String[] args = {"setsid", "bash", "-c", "echo $$"};
  267. shexec = new ShellCommandExecutor(args);
  268. shexec.execute();
  269. } catch (IOException ioe) {
  270. LOG.debug("setsid is not available on this machine. So not using it.");
  271. setsidSupported = false;
  272. } finally { // handle the exit code
  273. if (LOG.isDebugEnabled()) {
  274. LOG.debug("setsid exited with exit code "
  275. + (shexec != null ? shexec.getExitCode() : "(null executor)"));
  276. }
  277. }
  278. return setsidSupported;
  279. }
  280. /** Token separator regex used to parse Shell tool outputs */
  281. public static final String TOKEN_SEPARATOR_REGEX
  282. = WINDOWS ? "[|\n\r]" : "[ \t\n\r\f]";
  283. private long interval; // refresh interval in msec
  284. private long lastTime; // last time the command was performed
  285. private Map<String, String> environment; // env for the command execution
  286. private File dir;
  287. private Process process; // sub process used to execute the command
  288. private int exitCode;
  289. /**If or not script finished executing*/
  290. private volatile AtomicBoolean completed;
  291. public Shell() {
  292. this(0L);
  293. }
  294. /**
  295. * @param interval the minimum duration to wait before re-executing the
  296. * command.
  297. */
  298. public Shell( long interval ) {
  299. this.interval = interval;
  300. this.lastTime = (interval<0) ? 0 : -interval;
  301. }
  302. /** set the environment for the command
  303. * @param env Mapping of environment variables
  304. */
  305. protected void setEnvironment(Map<String, String> env) {
  306. this.environment = env;
  307. }
  308. /** set the working directory
  309. * @param dir The directory where the command would be executed
  310. */
  311. protected void setWorkingDirectory(File dir) {
  312. this.dir = dir;
  313. }
  314. /** check to see if a command needs to be executed and execute if needed */
  315. protected void run() throws IOException {
  316. if (lastTime + interval > Time.now())
  317. return;
  318. exitCode = 0; // reset for next run
  319. runCommand();
  320. }
  321. /** Run a command */
  322. private void runCommand() throws IOException {
  323. ProcessBuilder builder = new ProcessBuilder(getExecString());
  324. Timer timeOutTimer = null;
  325. ShellTimeoutTimerTask timeoutTimerTask = null;
  326. timedOut = new AtomicBoolean(false);
  327. completed = new AtomicBoolean(false);
  328. if (environment != null) {
  329. builder.environment().putAll(this.environment);
  330. }
  331. if (dir != null) {
  332. builder.directory(this.dir);
  333. }
  334. if (Shell.WINDOWS) {
  335. synchronized (WindowsProcessLaunchLock) {
  336. // To workaround the race condition issue with child processes
  337. // inheriting unintended handles during process launch that can
  338. // lead to hangs on reading output and error streams, we
  339. // serialize process creation. More info available at:
  340. // http://support.microsoft.com/kb/315939
  341. process = builder.start();
  342. }
  343. } else {
  344. process = builder.start();
  345. }
  346. if (timeOutInterval > 0) {
  347. timeOutTimer = new Timer("Shell command timeout");
  348. timeoutTimerTask = new ShellTimeoutTimerTask(
  349. this);
  350. //One time scheduling.
  351. timeOutTimer.schedule(timeoutTimerTask, timeOutInterval);
  352. }
  353. final BufferedReader errReader =
  354. new BufferedReader(new InputStreamReader(process
  355. .getErrorStream()));
  356. BufferedReader inReader =
  357. new BufferedReader(new InputStreamReader(process
  358. .getInputStream()));
  359. final StringBuffer errMsg = new StringBuffer();
  360. // read error and input streams as this would free up the buffers
  361. // free the error stream buffer
  362. Thread errThread = new Thread() {
  363. @Override
  364. public void run() {
  365. try {
  366. String line = errReader.readLine();
  367. while((line != null) && !isInterrupted()) {
  368. errMsg.append(line);
  369. errMsg.append(System.getProperty("line.separator"));
  370. line = errReader.readLine();
  371. }
  372. } catch(IOException ioe) {
  373. LOG.warn("Error reading the error stream", ioe);
  374. }
  375. }
  376. };
  377. try {
  378. errThread.start();
  379. } catch (IllegalStateException ise) { }
  380. try {
  381. parseExecResult(inReader); // parse the output
  382. // clear the input stream buffer
  383. String line = inReader.readLine();
  384. while(line != null) {
  385. line = inReader.readLine();
  386. }
  387. // wait for the process to finish and check the exit code
  388. exitCode = process.waitFor();
  389. try {
  390. // make sure that the error thread exits
  391. errThread.join();
  392. } catch (InterruptedException ie) {
  393. LOG.warn("Interrupted while reading the error stream", ie);
  394. }
  395. completed.set(true);
  396. //the timeout thread handling
  397. //taken care in finally block
  398. if (exitCode != 0) {
  399. // Log the commands that didn't work :-P
  400. StringBuffer commands = new StringBuffer();
  401. for(String cmd : builder.command()) {
  402. LOG.warn("Running Command: "+cmd);
  403. commands.append(cmd);
  404. commands.append("\n");
  405. }
  406. commands.append( errMsg.toString() );
  407. throw new ExitCodeException(exitCode, commands.toString());//errMsg.toString());
  408. }
  409. } catch (InterruptedException ie) {
  410. throw new IOException(ie.toString());
  411. } finally {
  412. if (timeOutTimer != null) {
  413. timeOutTimer.cancel();
  414. }
  415. // close the input stream
  416. try {
  417. inReader.close();
  418. } catch (IOException ioe) {
  419. LOG.warn("Error while closing the input stream", ioe);
  420. }
  421. try {
  422. if (!completed.get()) {
  423. errThread.interrupt();
  424. errThread.join();
  425. }
  426. } catch (InterruptedException ie) {
  427. LOG.warn("Interrupted while joining errThread");
  428. }
  429. try {
  430. errReader.close();
  431. } catch (IOException ioe) {
  432. LOG.warn("Error while closing the error stream", ioe);
  433. }
  434. process.destroy();
  435. lastTime = Time.now();
  436. }
  437. }
  438. /** return an array containing the command name & its parameters */
  439. protected abstract String[] getExecString();
  440. /** Parse the execution result */
  441. protected abstract void parseExecResult(BufferedReader lines)
  442. throws IOException;
  443. /** get the current sub-process executing the given command
  444. * @return process executing the command
  445. */
  446. public Process getProcess() {
  447. return process;
  448. }
  449. /** get the exit code
  450. * @return the exit code of the process
  451. */
  452. public int getExitCode() {
  453. return exitCode;
  454. }
  455. /**
  456. * This is an IOException with exit code added.
  457. */
  458. public static class ExitCodeException extends IOException {
  459. int exitCode;
  460. public ExitCodeException(int exitCode, String message) {
  461. super(message);
  462. this.exitCode = exitCode;
  463. }
  464. public int getExitCode() {
  465. return exitCode;
  466. }
  467. }
  468. /**
  469. * A simple shell command executor.
  470. *
  471. * <code>ShellCommandExecutor</code>should be used in cases where the output
  472. * of the command needs no explicit parsing and where the command, working
  473. * directory and the environment remains unchanged. The output of the command
  474. * is stored as-is and is expected to be small.
  475. */
  476. public static class ShellCommandExecutor extends Shell {
  477. private String[] command;
  478. private StringBuffer output;
  479. public ShellCommandExecutor(String[] execString) {
  480. this(execString, null);
  481. }
  482. public ShellCommandExecutor(String[] execString, File dir) {
  483. this(execString, dir, null);
  484. }
  485. public ShellCommandExecutor(String[] execString, File dir,
  486. Map<String, String> env) {
  487. this(execString, dir, env , 0L);
  488. }
  489. /**
  490. * Create a new instance of the ShellCommandExecutor to execute a command.
  491. *
  492. * @param execString The command to execute with arguments
  493. * @param dir If not-null, specifies the directory which should be set
  494. * as the current working directory for the command.
  495. * If null, the current working directory is not modified.
  496. * @param env If not-null, environment of the command will include the
  497. * key-value pairs specified in the map. If null, the current
  498. * environment is not modified.
  499. * @param timeout Specifies the time in milliseconds, after which the
  500. * command will be killed and the status marked as timedout.
  501. * If 0, the command will not be timed out.
  502. */
  503. public ShellCommandExecutor(String[] execString, File dir,
  504. Map<String, String> env, long timeout) {
  505. command = execString.clone();
  506. if (dir != null) {
  507. setWorkingDirectory(dir);
  508. }
  509. if (env != null) {
  510. setEnvironment(env);
  511. }
  512. timeOutInterval = timeout;
  513. }
  514. /** Execute the shell command. */
  515. public void execute() throws IOException {
  516. this.run();
  517. }
  518. @Override
  519. public String[] getExecString() {
  520. return command;
  521. }
  522. @Override
  523. protected void parseExecResult(BufferedReader lines) throws IOException {
  524. output = new StringBuffer();
  525. char[] buf = new char[512];
  526. int nRead;
  527. while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {
  528. output.append(buf, 0, nRead);
  529. }
  530. }
  531. /** Get the output of the shell command.*/
  532. public String getOutput() {
  533. return (output == null) ? "" : output.toString();
  534. }
  535. /**
  536. * Returns the commands of this instance.
  537. * Arguments with spaces in are presented with quotes round; other
  538. * arguments are presented raw
  539. *
  540. * @return a string representation of the object.
  541. */
  542. @Override
  543. public String toString() {
  544. StringBuilder builder = new StringBuilder();
  545. String[] args = getExecString();
  546. for (String s : args) {
  547. if (s.indexOf(' ') >= 0) {
  548. builder.append('"').append(s).append('"');
  549. } else {
  550. builder.append(s);
  551. }
  552. builder.append(' ');
  553. }
  554. return builder.toString();
  555. }
  556. }
  557. /**
  558. * To check if the passed script to shell command executor timed out or
  559. * not.
  560. *
  561. * @return if the script timed out.
  562. */
  563. public boolean isTimedOut() {
  564. return timedOut.get();
  565. }
  566. /**
  567. * Set if the command has timed out.
  568. *
  569. */
  570. private void setTimedOut() {
  571. this.timedOut.set(true);
  572. }
  573. /**
  574. * Static method to execute a shell command.
  575. * Covers most of the simple cases without requiring the user to implement
  576. * the <code>Shell</code> interface.
  577. * @param cmd shell command to execute.
  578. * @return the output of the executed command.
  579. */
  580. public static String execCommand(String ... cmd) throws IOException {
  581. return execCommand(null, cmd, 0L);
  582. }
  583. /**
  584. * Static method to execute a shell command.
  585. * Covers most of the simple cases without requiring the user to implement
  586. * the <code>Shell</code> interface.
  587. * @param env the map of environment key=value
  588. * @param cmd shell command to execute.
  589. * @param timeout time in milliseconds after which script should be marked timeout
  590. * @return the output of the executed command.o
  591. */
  592. public static String execCommand(Map<String, String> env, String[] cmd,
  593. long timeout) throws IOException {
  594. ShellCommandExecutor exec = new ShellCommandExecutor(cmd, null, env,
  595. timeout);
  596. exec.execute();
  597. return exec.getOutput();
  598. }
  599. /**
  600. * Static method to execute a shell command.
  601. * Covers most of the simple cases without requiring the user to implement
  602. * the <code>Shell</code> interface.
  603. * @param env the map of environment key=value
  604. * @param cmd shell command to execute.
  605. * @return the output of the executed command.
  606. */
  607. public static String execCommand(Map<String,String> env, String ... cmd)
  608. throws IOException {
  609. return execCommand(env, cmd, 0L);
  610. }
  611. /**
  612. * Timer which is used to timeout scripts spawned off by shell.
  613. */
  614. private static class ShellTimeoutTimerTask extends TimerTask {
  615. private Shell shell;
  616. public ShellTimeoutTimerTask(Shell shell) {
  617. this.shell = shell;
  618. }
  619. @Override
  620. public void run() {
  621. Process p = shell.getProcess();
  622. try {
  623. p.exitValue();
  624. } catch (Exception e) {
  625. //Process has not terminated.
  626. //So check if it has completed
  627. //if not just destroy it.
  628. if (p != null && !shell.completed.get()) {
  629. shell.setTimedOut();
  630. p.destroy();
  631. }
  632. }
  633. }
  634. }
  635. }