1 /* 2 * Copyright (C) 2010 The Android Open Source Project 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 17 package com.android.tradefed.util; 18 19 import com.android.tradefed.log.LogUtil.CLog; 20 21 import com.google.common.annotations.VisibleForTesting; 22 23 import java.io.BufferedOutputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.io.File; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.util.Arrays; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.Timer; 36 import java.util.TimerTask; 37 import java.util.concurrent.CountDownLatch; 38 import java.util.concurrent.TimeUnit; 39 40 /** 41 * A collection of helper methods for executing operations. 42 */ 43 public class RunUtil implements IRunUtil { 44 45 public static final String RUNNABLE_NOTIFIER_NAME = "RunnableNotifier"; 46 public static final String INHERITIO_PREFIX = "inheritio-"; 47 48 private static final int POLL_TIME_INCREASE_FACTOR = 4; 49 private static final long THREAD_JOIN_POLL_INTERVAL = 30 * 1000; 50 private static final long IO_THREAD_JOIN_INTERVAL = 5 * 1000; 51 private static final long PROCESS_DESTROY_TIMEOUT_SEC = 2; 52 private static IRunUtil sDefaultInstance = null; 53 private File mWorkingDir = null; 54 private Map<String, String> mEnvVariables = new HashMap<String, String>(); 55 private Set<String> mUnsetEnvVariables = new HashSet<String>(); 56 private EnvPriority mEnvVariablePriority = EnvPriority.UNSET; 57 private ThreadLocal<Boolean> mIsInterruptAllowed = new ThreadLocal<Boolean>() { 58 @Override 59 protected Boolean initialValue() { 60 return Boolean.FALSE; 61 } 62 }; 63 private Map<Long, String> mInterruptThreads = new HashMap<>(); 64 private ThreadLocal<Timer> mWatchdogInterrupt = null; 65 private boolean mInterruptibleGlobal = false; 66 67 /** 68 * Create a new {@link RunUtil} object to use. 69 */ RunUtil()70 public RunUtil() { 71 } 72 73 /** 74 * Get a reference to the default {@link RunUtil} object. 75 * <p/> 76 * This is useful for callers who want to use IRunUtil without customization. 77 * Its recommended that callers who do need a custom IRunUtil instance 78 * (ie need to call either {@link #setEnvVariable(String, String)} or 79 * {@link #setWorkingDir(File)} create their own copy. 80 */ getDefault()81 public static IRunUtil getDefault() { 82 if (sDefaultInstance == null) { 83 sDefaultInstance = new RunUtil(); 84 } 85 return sDefaultInstance; 86 } 87 88 /** 89 * {@inheritDoc} 90 */ 91 @Override setWorkingDir(File dir)92 public synchronized void setWorkingDir(File dir) { 93 if (this.equals(sDefaultInstance)) { 94 throw new UnsupportedOperationException("Cannot setWorkingDir on default RunUtil"); 95 } 96 mWorkingDir = dir; 97 } 98 99 /** 100 * {@inheritDoc} 101 */ 102 @Override setEnvVariable(String name, String value)103 public synchronized void setEnvVariable(String name, String value) { 104 if (this.equals(sDefaultInstance)) { 105 throw new UnsupportedOperationException("Cannot setEnvVariable on default RunUtil"); 106 } 107 mEnvVariables.put(name, value); 108 } 109 110 /** 111 * {@inheritDoc} 112 * Environment variables may inherit from the parent process, so we need to delete 113 * the environment variable from {@link ProcessBuilder#environment()} 114 * 115 * @param key the variable name 116 * @see ProcessBuilder#environment() 117 */ 118 @Override unsetEnvVariable(String key)119 public synchronized void unsetEnvVariable(String key) { 120 if (this.equals(sDefaultInstance)) { 121 throw new UnsupportedOperationException("Cannot unsetEnvVariable on default RunUtil"); 122 } 123 mUnsetEnvVariables.add(key); 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override runTimedCmd(final long timeout, final String... command)130 public CommandResult runTimedCmd(final long timeout, final String... command) { 131 return runTimedCmd(timeout, null, null, true, command); 132 } 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override runTimedCmd(final long timeout, OutputStream stdout, OutputStream stderr, final String... command)138 public CommandResult runTimedCmd(final long timeout, OutputStream stdout, 139 OutputStream stderr, final String... command) { 140 return runTimedCmd(timeout, stdout, stderr, false, command); 141 } 142 143 /** 144 * Helper method to do a runTimeCmd call with or without outputStream specified. 145 * 146 * @return a {@CommandResult} containing results from command 147 */ runTimedCmd(final long timeout, OutputStream stdout, OutputStream stderr, boolean closeStreamAfterRun, final String... command)148 private CommandResult runTimedCmd(final long timeout, OutputStream stdout, 149 OutputStream stderr, boolean closeStreamAfterRun, final String... command) { 150 final CommandResult result = new CommandResult(); 151 IRunUtil.IRunnableResult osRunnable = 152 createRunnableResult(result, stdout, stderr, closeStreamAfterRun, command); 153 CommandStatus status = runTimed(timeout, osRunnable, true); 154 result.setStatus(status); 155 return result; 156 } 157 158 /** 159 * Create a {@link com.android.tradefed.util.IRunUtil.IRunnableResult} that will run the 160 * command. 161 */ 162 @VisibleForTesting createRunnableResult( CommandResult result, OutputStream stdout, OutputStream stderr, boolean closeStreamAfterRun, String... command)163 IRunUtil.IRunnableResult createRunnableResult( 164 CommandResult result, 165 OutputStream stdout, 166 OutputStream stderr, 167 boolean closeStreamAfterRun, 168 String... command) { 169 return new RunnableResult( 170 result, null, createProcessBuilder(command), stdout, stderr, closeStreamAfterRun); 171 } 172 173 /** {@inheritDoc} */ 174 @Override runTimedCmdRetry( long timeout, long retryInterval, int attempts, String... command)175 public CommandResult runTimedCmdRetry( 176 long timeout, long retryInterval, int attempts, String... command) { 177 CommandResult result = null; 178 int counter = 0; 179 while (counter < attempts) { 180 result = runTimedCmd(timeout, command); 181 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 182 return result; 183 } 184 sleep(retryInterval); 185 counter++; 186 } 187 return result; 188 } 189 createProcessBuilder(String... command)190 private synchronized ProcessBuilder createProcessBuilder(String... command) { 191 return createProcessBuilder(Arrays.asList(command)); 192 } 193 createProcessBuilder(List<String> commandList)194 private synchronized ProcessBuilder createProcessBuilder(List<String> commandList) { 195 ProcessBuilder processBuilder = new ProcessBuilder(); 196 if (mWorkingDir != null) { 197 processBuilder.directory(mWorkingDir); 198 } 199 // By default unset an env. for process has higher priority, but in some case we might want 200 // the 'set' to have priority. 201 if (EnvPriority.UNSET.equals(mEnvVariablePriority)) { 202 if (!mEnvVariables.isEmpty()) { 203 processBuilder.environment().putAll(mEnvVariables); 204 } 205 if (!mUnsetEnvVariables.isEmpty()) { 206 // in this implementation, the unsetEnv's priority is higher than set. 207 processBuilder.environment().keySet().removeAll(mUnsetEnvVariables); 208 } 209 } else { 210 if (!mUnsetEnvVariables.isEmpty()) { 211 processBuilder.environment().keySet().removeAll(mUnsetEnvVariables); 212 } 213 if (!mEnvVariables.isEmpty()) { 214 // in this implementation, the setEnv's priority is higher than set. 215 processBuilder.environment().putAll(mEnvVariables); 216 } 217 } 218 return processBuilder.command(commandList); 219 } 220 221 /** 222 * {@inheritDoc} 223 */ 224 @Override runTimedCmdWithInput(final long timeout, String input, final String... command)225 public CommandResult runTimedCmdWithInput(final long timeout, String input, 226 final String... command) { 227 return runTimedCmdWithInput(timeout, input, ArrayUtil.list(command)); 228 } 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override runTimedCmdWithInput(final long timeout, String input, final List<String> command)234 public CommandResult runTimedCmdWithInput(final long timeout, String input, 235 final List<String> command) { 236 final CommandResult result = new CommandResult(); 237 IRunUtil.IRunnableResult osRunnable = new RunnableResult(result, input, 238 createProcessBuilder(command)); 239 CommandStatus status = runTimed(timeout, osRunnable, true); 240 result.setStatus(status); 241 return result; 242 } 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override runTimedCmdSilently(final long timeout, final String... command)248 public CommandResult runTimedCmdSilently(final long timeout, final String... command) { 249 final CommandResult result = new CommandResult(); 250 IRunUtil.IRunnableResult osRunnable = new RunnableResult(result, null, 251 createProcessBuilder(command)); 252 CommandStatus status = runTimed(timeout, osRunnable, false); 253 result.setStatus(status); 254 return result; 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override runTimedCmdSilentlyRetry(long timeout, long retryInterval, int attempts, String... command)261 public CommandResult runTimedCmdSilentlyRetry(long timeout, long retryInterval, int attempts, 262 String... command) { 263 CommandResult result = null; 264 int counter = 0; 265 while (counter < attempts) { 266 result = runTimedCmdSilently(timeout, command); 267 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 268 return result; 269 } 270 sleep(retryInterval); 271 counter++; 272 } 273 return result; 274 } 275 276 /** 277 * {@inheritDoc} 278 */ 279 @Override runCmdInBackground(final String... command)280 public Process runCmdInBackground(final String... command) throws IOException { 281 final String fullCmd = Arrays.toString(command); 282 CLog.v("Running %s", fullCmd); 283 return createProcessBuilder(command).start(); 284 } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override runCmdInBackground(final List<String> command)290 public Process runCmdInBackground(final List<String> command) throws IOException { 291 CLog.v("Running %s", command); 292 return createProcessBuilder(command).start(); 293 } 294 295 /** 296 * {@inheritDoc} 297 */ 298 @Override runCmdInBackground(List<String> command, OutputStream output)299 public Process runCmdInBackground(List<String> command, OutputStream output) 300 throws IOException { 301 CLog.v("Running %s", command); 302 Process process = createProcessBuilder(command).start(); 303 inheritIO( 304 process.getInputStream(), 305 output, 306 String.format(INHERITIO_PREFIX + "stdout-%s", command)); 307 inheritIO( 308 process.getErrorStream(), 309 output, 310 String.format(INHERITIO_PREFIX + "stderr-%s", command)); 311 return process; 312 } 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override runTimed(long timeout, IRunUtil.IRunnableResult runnable, boolean logErrors)318 public CommandStatus runTimed(long timeout, IRunUtil.IRunnableResult runnable, 319 boolean logErrors) { 320 checkInterrupted(); 321 RunnableNotifier runThread = new RunnableNotifier(runnable, logErrors); 322 if (logErrors) { 323 if (timeout > 0l) { 324 CLog.d("Running command with timeout: %dms", timeout); 325 } else { 326 CLog.d("Running command without timeout."); 327 } 328 } 329 runThread.start(); 330 long startTime = System.currentTimeMillis(); 331 long pollIterval = 0; 332 if (timeout > 0l && timeout < THREAD_JOIN_POLL_INTERVAL) { 333 // only set the pollInterval if we have a timeout 334 pollIterval = timeout; 335 } else { 336 pollIterval = THREAD_JOIN_POLL_INTERVAL; 337 } 338 do { 339 try { 340 runThread.join(pollIterval); 341 } catch (InterruptedException e) { 342 if (mIsInterruptAllowed.get()) { 343 CLog.i("runTimed: interrupted while joining the runnable"); 344 break; 345 } 346 else { 347 CLog.i("runTimed: received an interrupt but uninterruptible mode, ignoring"); 348 } 349 } 350 checkInterrupted(); 351 } while ((timeout == 0l || (System.currentTimeMillis() - startTime) < timeout) 352 && runThread.isAlive()); 353 // Snapshot the status when out of the run loop because thread may terminate and return a 354 // false FAILED instead of TIMED_OUT. 355 CommandStatus status = runThread.getStatus(); 356 if (CommandStatus.TIMED_OUT.equals(status) || CommandStatus.EXCEPTION.equals(status)) { 357 CLog.i("runTimed: Calling interrupt, status is %s", status); 358 runThread.cancel(); 359 } 360 checkInterrupted(); 361 return status; 362 } 363 364 /** 365 * {@inheritDoc} 366 */ 367 @Override runTimedRetry(long opTimeout, long pollInterval, int attempts, IRunUtil.IRunnableResult runnable)368 public boolean runTimedRetry(long opTimeout, long pollInterval, int attempts, 369 IRunUtil.IRunnableResult runnable) { 370 for (int i = 0; i < attempts; i++) { 371 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 372 return true; 373 } 374 CLog.d("operation failed, waiting for %d ms", pollInterval); 375 sleep(pollInterval); 376 } 377 return false; 378 } 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override runFixedTimedRetry(final long opTimeout, final long pollInterval, final long maxTime, final IRunUtil.IRunnableResult runnable)384 public boolean runFixedTimedRetry(final long opTimeout, final long pollInterval, 385 final long maxTime, final IRunUtil.IRunnableResult runnable) { 386 final long initialTime = getCurrentTime(); 387 while (getCurrentTime() < (initialTime + maxTime)) { 388 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 389 return true; 390 } 391 CLog.d("operation failed, waiting for %d ms", pollInterval); 392 sleep(pollInterval); 393 } 394 return false; 395 } 396 397 /** 398 * {@inheritDoc} 399 */ 400 @Override runEscalatingTimedRetry(final long opTimeout, final long initialPollInterval, final long maxPollInterval, final long maxTime, final IRunUtil.IRunnableResult runnable)401 public boolean runEscalatingTimedRetry(final long opTimeout, 402 final long initialPollInterval, final long maxPollInterval, final long maxTime, 403 final IRunUtil.IRunnableResult runnable) { 404 // wait an initial time provided 405 long pollInterval = initialPollInterval; 406 final long initialTime = getCurrentTime(); 407 while (true) { 408 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 409 return true; 410 } 411 long remainingTime = maxTime - (getCurrentTime() - initialTime); 412 if (remainingTime <= 0) { 413 CLog.d("operation is still failing after retrying for %d ms", maxTime); 414 return false; 415 } else if (remainingTime < pollInterval) { 416 // cap pollInterval to a max of remainingTime 417 pollInterval = remainingTime; 418 } 419 CLog.d("operation failed, waiting for %d ms", pollInterval); 420 sleep(pollInterval); 421 // somewhat arbitrarily, increase the poll time by a factor of 4 for each attempt, 422 // up to the previously decided maximum 423 pollInterval *= POLL_TIME_INCREASE_FACTOR; 424 if (pollInterval > maxPollInterval) { 425 pollInterval = maxPollInterval; 426 } 427 } 428 } 429 430 /** 431 * Retrieves the current system clock time. 432 * <p/> 433 * Exposed so it can be mocked for unit testing 434 */ getCurrentTime()435 long getCurrentTime() { 436 return System.currentTimeMillis(); 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override sleep(long time)443 public void sleep(long time) { 444 checkInterrupted(); 445 if (time <= 0) { 446 return; 447 } 448 try { 449 Thread.sleep(time); 450 } catch (InterruptedException e) { 451 // ignore 452 CLog.d("sleep interrupted"); 453 } 454 checkInterrupted(); 455 } 456 457 /** 458 * {@inheritDoc} 459 */ 460 @Override allowInterrupt(boolean allow)461 public void allowInterrupt(boolean allow) { 462 CLog.d("run interrupt allowed: %s", allow); 463 mIsInterruptAllowed.set(allow); 464 checkInterrupted(); 465 } 466 467 /** 468 * {@inheritDoc} 469 */ 470 @Override isInterruptAllowed()471 public boolean isInterruptAllowed() { 472 return mIsInterruptAllowed.get(); 473 } 474 475 /** 476 * {@inheritDoc} 477 */ 478 @Override setInterruptibleInFuture(Thread thread, final long timeMs)479 public void setInterruptibleInFuture(Thread thread, final long timeMs) { 480 if (mWatchdogInterrupt == null) { 481 mWatchdogInterrupt = new ThreadLocal<Timer>() { 482 @Override 483 protected Timer initialValue() { 484 return new Timer(true); 485 } 486 }; 487 CLog.w("Setting future interruption in %s ms", timeMs); 488 mWatchdogInterrupt.get().schedule(new InterruptTask(thread), timeMs); 489 } else { 490 CLog.w("Future interruptible state already set."); 491 } 492 } 493 494 /** 495 * {@inheritDoc} 496 */ 497 @Override interrupt(Thread thread, String message)498 public synchronized void interrupt(Thread thread, String message) { 499 if (message == null) { 500 throw new IllegalArgumentException("message cannot be null."); 501 } 502 mInterruptThreads.put(thread.getId(), message); 503 } 504 checkInterrupted()505 private synchronized void checkInterrupted() { 506 final long threadId = Thread.currentThread().getId(); 507 if (mInterruptibleGlobal) { 508 // If the global flag is on, meaning everything must terminate. 509 if (!isInterruptAllowed()) { 510 allowInterrupt(true); 511 } 512 } 513 if (isInterruptAllowed()) { 514 final String message = mInterruptThreads.remove(threadId); 515 if (message != null) { 516 Thread.currentThread().interrupt(); 517 throw new RunInterruptedException(message); 518 } 519 } 520 } 521 522 /** 523 * Helper thread that wraps a runnable, and notifies when done. 524 */ 525 private static class RunnableNotifier extends Thread { 526 527 private final IRunUtil.IRunnableResult mRunnable; 528 private CommandStatus mStatus = CommandStatus.TIMED_OUT; 529 private boolean mLogErrors = true; 530 RunnableNotifier(IRunUtil.IRunnableResult runnable, boolean logErrors)531 RunnableNotifier(IRunUtil.IRunnableResult runnable, boolean logErrors) { 532 // Set this thread to be a daemon so that it does not prevent 533 // TF from shutting down. 534 setName(RUNNABLE_NOTIFIER_NAME); 535 setDaemon(true); 536 mRunnable = runnable; 537 mLogErrors = logErrors; 538 } 539 540 @Override run()541 public void run() { 542 CommandStatus status; 543 try { 544 status = mRunnable.run() ? CommandStatus.SUCCESS : CommandStatus.FAILED; 545 } catch (InterruptedException e) { 546 CLog.i("runutil interrupted"); 547 status = CommandStatus.EXCEPTION; 548 } catch (Exception e) { 549 if (mLogErrors) { 550 CLog.e("Exception occurred when executing runnable"); 551 CLog.e(e); 552 } 553 status = CommandStatus.EXCEPTION; 554 } 555 synchronized (this) { 556 mStatus = status; 557 } 558 } 559 cancel()560 public void cancel() { 561 mRunnable.cancel(); 562 } 563 getStatus()564 synchronized CommandStatus getStatus() { 565 return mStatus; 566 } 567 } 568 569 class RunnableResult implements IRunUtil.IRunnableResult { 570 private final ProcessBuilder mProcessBuilder; 571 private final CommandResult mCommandResult; 572 private final String mInput; 573 private Process mProcess = null; 574 private CountDownLatch mCountDown = null; 575 private Thread mExecutionThread; 576 private OutputStream stdOut = null; 577 private OutputStream stdErr = null; 578 private final boolean mCloseStreamAfterRun; 579 private Object mLock = new Object(); 580 private boolean mCancelled = false; 581 RunnableResult(final CommandResult result, final String input, final ProcessBuilder processBuilder)582 RunnableResult(final CommandResult result, final String input, 583 final ProcessBuilder processBuilder) { 584 this(result, input, processBuilder, null, null, false); 585 stdOut = new ByteArrayOutputStream(); 586 stdErr = new ByteArrayOutputStream(); 587 } 588 589 /** 590 * Alternative constructor that allows redirecting the output to any Outputstream. 591 * Stdout and stderr can be independently redirected to different Outputstream 592 * implementations. 593 * If streams are null, default behavior of using a buffer will be used. 594 */ RunnableResult(final CommandResult result, final String input, final ProcessBuilder processBuilder, OutputStream stdoutStream, OutputStream stderrStream, boolean closeStreamAfterRun)595 RunnableResult(final CommandResult result, final String input, 596 final ProcessBuilder processBuilder, OutputStream stdoutStream, 597 OutputStream stderrStream, boolean closeStreamAfterRun) { 598 mCloseStreamAfterRun = closeStreamAfterRun; 599 mProcessBuilder = processBuilder; 600 mInput = input; 601 mCommandResult = result; 602 // Ensure the outputs are never null 603 mCommandResult.setStdout(""); 604 mCommandResult.setStderr(""); 605 mCountDown = new CountDownLatch(1); 606 // Redirect IO, so that the outputstream for the spawn process does not fill up 607 // and cause deadlock. 608 if (stdoutStream != null) { 609 stdOut = stdoutStream; 610 } else { 611 stdOut = new ByteArrayOutputStream(); 612 } 613 if (stderrStream != null) { 614 stdErr = stderrStream; 615 } else { 616 stdErr = new ByteArrayOutputStream(); 617 } 618 } 619 620 /** Start a {@link Process} based on the {@link ProcessBuilder}. */ 621 @VisibleForTesting startProcess()622 Process startProcess() throws IOException { 623 return mProcessBuilder.start(); 624 } 625 626 @Override run()627 public boolean run() throws Exception { 628 Thread stdoutThread = null; 629 Thread stderrThread = null; 630 synchronized (mLock) { 631 if (mCancelled == true) { 632 // if cancel() was called before run() took the lock, we do not even attempt 633 // to run. 634 return false; 635 } 636 mExecutionThread = Thread.currentThread(); 637 CLog.d("Running %s", mProcessBuilder.command()); 638 mProcess = startProcess(); 639 if (mInput != null) { 640 BufferedOutputStream processStdin = 641 new BufferedOutputStream(mProcess.getOutputStream()); 642 processStdin.write(mInput.getBytes("UTF-8")); 643 processStdin.flush(); 644 processStdin.close(); 645 } 646 // Log the command for thread tracking purpose. 647 stdoutThread = 648 inheritIO( 649 mProcess.getInputStream(), 650 stdOut, 651 String.format("inheritio-stdout-%s", mProcessBuilder.command())); 652 stderrThread = 653 inheritIO( 654 mProcess.getErrorStream(), 655 stdErr, 656 String.format("inheritio-stderr-%s", mProcessBuilder.command())); 657 } 658 // Wait for process to complete. 659 int rc = Integer.MIN_VALUE; 660 try { 661 try { 662 rc = mProcess.waitFor(); 663 // wait for stdout and stderr to be read 664 stdoutThread.join(IO_THREAD_JOIN_INTERVAL); 665 if (stdoutThread.isAlive()) { 666 CLog.d("stdout read thread %s still alive.", stdoutThread.toString()); 667 } 668 stderrThread.join(IO_THREAD_JOIN_INTERVAL); 669 if (stderrThread.isAlive()) { 670 CLog.d("stderr read thread %s still alive.", stderrThread.toString()); 671 } 672 // close the buffer that holds stdout/err content if default stream 673 // stream specified by caller should be handled by the caller. 674 if (mCloseStreamAfterRun) { 675 stdOut.close(); 676 stdErr.close(); 677 } 678 } finally { 679 // Write out the streams to the result. 680 if (stdOut instanceof ByteArrayOutputStream) { 681 mCommandResult.setStdout(((ByteArrayOutputStream)stdOut).toString("UTF-8")); 682 } else { 683 mCommandResult.setStdout("redirected to " + 684 stdOut.getClass().getSimpleName()); 685 } 686 if (stdErr instanceof ByteArrayOutputStream) { 687 mCommandResult.setStderr(((ByteArrayOutputStream)stdErr).toString("UTF-8")); 688 } else { 689 mCommandResult.setStderr("redirected to " + 690 stdErr.getClass().getSimpleName()); 691 } 692 } 693 } finally { 694 mCountDown.countDown(); 695 } 696 697 if (rc == 0) { 698 return true; 699 } else { 700 CLog.d("%s command failed. return code %d", mProcessBuilder.command(), rc); 701 } 702 return false; 703 } 704 705 @Override cancel()706 public void cancel() { 707 mCancelled = true; 708 synchronized (mLock) { 709 if (mProcess == null) { 710 return; 711 } 712 CLog.i("Cancelling the process execution"); 713 mProcess.destroy(); 714 try { 715 // Only allow to continue if the Stdout has been read 716 // RunnableNotifier#Interrupt is the next call and will terminate the thread 717 if (!mCountDown.await(PROCESS_DESTROY_TIMEOUT_SEC, TimeUnit.SECONDS)) { 718 CLog.i("Process still not terminated, interrupting the execution thread"); 719 mExecutionThread.interrupt(); 720 mCountDown.await(); 721 } 722 } catch (InterruptedException e) { 723 CLog.i("interrupted while waiting for process output to be saved"); 724 } 725 } 726 } 727 728 @Override toString()729 public String toString() { 730 return "RunnableResult [command=" 731 + ((mProcessBuilder != null) ? mProcessBuilder.command() : null) 732 + "]"; 733 } 734 } 735 736 /** 737 * Helper method to redirect input stream. 738 * 739 * @param src {@link InputStream} to inherit/redirect from 740 * @param dest {@link BufferedOutputStream} to inherit/redirect to 741 * @param name the name of the thread returned. 742 * @return a {@link Thread} started that receives the IO. 743 */ inheritIO(final InputStream src, final OutputStream dest, String name)744 private static Thread inheritIO(final InputStream src, final OutputStream dest, String name) { 745 Thread t = new Thread(new Runnable() { 746 @Override 747 public void run() { 748 try { 749 StreamUtil.copyStreams(src, dest); 750 } catch (IOException e) { 751 CLog.e("Failed to read input stream."); 752 } 753 } 754 }); 755 t.setName(name); 756 t.start(); 757 return t; 758 } 759 760 /** Allow to stop the Timer Thread for the run util instance if started. */ 761 @VisibleForTesting terminateTimer()762 void terminateTimer() { 763 if (mWatchdogInterrupt.get() != null) { 764 mWatchdogInterrupt.get().purge(); 765 mWatchdogInterrupt.get().cancel(); 766 } 767 } 768 769 /** Timer that will execute a interrupt on the Thread registered. */ 770 private class InterruptTask extends TimerTask { 771 772 private Thread mToInterrupt = null; 773 InterruptTask(Thread t)774 public InterruptTask(Thread t) { 775 mToInterrupt = t; 776 } 777 778 @Override run()779 public void run() { 780 if (mToInterrupt != null) { 781 CLog.e("Interrupting with TimerTask"); 782 mInterruptibleGlobal = true; 783 mToInterrupt.interrupt(); 784 } 785 } 786 } 787 788 /** 789 * {@inheritDoc} 790 */ 791 @Override setEnvVariablePriority(EnvPriority priority)792 public void setEnvVariablePriority(EnvPriority priority) { 793 if (this.equals(sDefaultInstance)) { 794 throw new UnsupportedOperationException("Cannot setWorkingDir on default RunUtil"); 795 } 796 mEnvVariablePriority = priority; 797 } 798 } 799