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 */ 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 */ 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 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 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 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 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 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 */ 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 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 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 190 private synchronized ProcessBuilder createProcessBuilder(String... command) { 191 return createProcessBuilder(Arrays.asList(command)); 192 } 193 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 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 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 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 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 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 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 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 /** 316 * {@inheritDoc} 317 */ 318 @Override 319 public CommandStatus runTimed(long timeout, IRunUtil.IRunnableResult runnable, 320 boolean logErrors) { 321 checkInterrupted(); 322 RunnableNotifier runThread = new RunnableNotifier(runnable, logErrors); 323 CLog.d("Running command with timeout: %dms", timeout); 324 runThread.start(); 325 long startTime = System.currentTimeMillis(); 326 long pollIterval = 0; 327 if (timeout < THREAD_JOIN_POLL_INTERVAL) { 328 pollIterval = timeout; 329 } else { 330 pollIterval = THREAD_JOIN_POLL_INTERVAL; 331 } 332 do { 333 try { 334 runThread.join(pollIterval); 335 } catch (InterruptedException e) { 336 if (mIsInterruptAllowed.get()) { 337 CLog.i("runTimed: interrupted while joining the runnable"); 338 break; 339 } 340 else { 341 CLog.i("runTimed: received an interrupt but uninterruptible mode, ignoring"); 342 } 343 } 344 checkInterrupted(); 345 } while ((System.currentTimeMillis() - startTime) < timeout && runThread.isAlive()); 346 // Snapshot the status when out of the run loop because thread may terminate and return a 347 // false FAILED instead of TIMED_OUT. 348 CommandStatus status = runThread.getStatus(); 349 if (CommandStatus.TIMED_OUT.equals(status) || CommandStatus.EXCEPTION.equals(status)) { 350 CLog.i("runTimed: Calling interrupt, status is %s", status); 351 runThread.cancel(); 352 } 353 checkInterrupted(); 354 return status; 355 } 356 357 /** 358 * {@inheritDoc} 359 */ 360 @Override 361 public boolean runTimedRetry(long opTimeout, long pollInterval, int attempts, 362 IRunUtil.IRunnableResult runnable) { 363 for (int i = 0; i < attempts; i++) { 364 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 365 return true; 366 } 367 CLog.d("operation failed, waiting for %d ms", pollInterval); 368 sleep(pollInterval); 369 } 370 return false; 371 } 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override 377 public boolean runFixedTimedRetry(final long opTimeout, final long pollInterval, 378 final long maxTime, final IRunUtil.IRunnableResult runnable) { 379 final long initialTime = getCurrentTime(); 380 while (getCurrentTime() < (initialTime + maxTime)) { 381 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 382 return true; 383 } 384 CLog.d("operation failed, waiting for %d ms", pollInterval); 385 sleep(pollInterval); 386 } 387 return false; 388 } 389 390 /** 391 * {@inheritDoc} 392 */ 393 @Override 394 public boolean runEscalatingTimedRetry(final long opTimeout, 395 final long initialPollInterval, final long maxPollInterval, final long maxTime, 396 final IRunUtil.IRunnableResult runnable) { 397 // wait an initial time provided 398 long pollInterval = initialPollInterval; 399 final long initialTime = getCurrentTime(); 400 while (true) { 401 if (runTimed(opTimeout, runnable, true) == CommandStatus.SUCCESS) { 402 return true; 403 } 404 long remainingTime = maxTime - (getCurrentTime() - initialTime); 405 if (remainingTime <= 0) { 406 CLog.d("operation is still failing after retrying for %d ms", maxTime); 407 return false; 408 } else if (remainingTime < pollInterval) { 409 // cap pollInterval to a max of remainingTime 410 pollInterval = remainingTime; 411 } 412 CLog.d("operation failed, waiting for %d ms", pollInterval); 413 sleep(pollInterval); 414 // somewhat arbitrarily, increase the poll time by a factor of 4 for each attempt, 415 // up to the previously decided maximum 416 pollInterval *= POLL_TIME_INCREASE_FACTOR; 417 if (pollInterval > maxPollInterval) { 418 pollInterval = maxPollInterval; 419 } 420 } 421 } 422 423 /** 424 * Retrieves the current system clock time. 425 * <p/> 426 * Exposed so it can be mocked for unit testing 427 */ 428 long getCurrentTime() { 429 return System.currentTimeMillis(); 430 } 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override 436 public void sleep(long time) { 437 checkInterrupted(); 438 if (time <= 0) { 439 return; 440 } 441 try { 442 Thread.sleep(time); 443 } catch (InterruptedException e) { 444 // ignore 445 CLog.d("sleep interrupted"); 446 } 447 checkInterrupted(); 448 } 449 450 /** 451 * {@inheritDoc} 452 */ 453 @Override 454 public void allowInterrupt(boolean allow) { 455 CLog.d("run interrupt allowed: %s", allow); 456 mIsInterruptAllowed.set(allow); 457 checkInterrupted(); 458 } 459 460 /** 461 * {@inheritDoc} 462 */ 463 @Override 464 public boolean isInterruptAllowed() { 465 return mIsInterruptAllowed.get(); 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override 472 public void setInterruptibleInFuture(Thread thread, final long timeMs) { 473 if (mWatchdogInterrupt == null) { 474 mWatchdogInterrupt = new ThreadLocal<Timer>() { 475 @Override 476 protected Timer initialValue() { 477 return new Timer(true); 478 } 479 }; 480 CLog.w("Setting future interruption in %s ms", timeMs); 481 mWatchdogInterrupt.get().schedule(new InterruptTask(thread), timeMs); 482 } else { 483 CLog.w("Future interruptible state already set."); 484 } 485 } 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override 491 public synchronized void interrupt(Thread thread, String message) { 492 if (message == null) { 493 throw new IllegalArgumentException("message cannot be null."); 494 } 495 mInterruptThreads.put(thread.getId(), message); 496 } 497 498 private synchronized void checkInterrupted() { 499 final long threadId = Thread.currentThread().getId(); 500 if (mInterruptibleGlobal) { 501 // If the global flag is on, meaning everything must terminate. 502 if (!isInterruptAllowed()) { 503 allowInterrupt(true); 504 } 505 } 506 if (isInterruptAllowed()) { 507 final String message = mInterruptThreads.remove(threadId); 508 if (message != null) { 509 Thread.currentThread().interrupt(); 510 throw new RunInterruptedException(message); 511 } 512 } 513 } 514 515 /** 516 * Helper thread that wraps a runnable, and notifies when done. 517 */ 518 private static class RunnableNotifier extends Thread { 519 520 private final IRunUtil.IRunnableResult mRunnable; 521 private CommandStatus mStatus = CommandStatus.TIMED_OUT; 522 private boolean mLogErrors = true; 523 524 RunnableNotifier(IRunUtil.IRunnableResult runnable, boolean logErrors) { 525 // Set this thread to be a daemon so that it does not prevent 526 // TF from shutting down. 527 setName(RUNNABLE_NOTIFIER_NAME); 528 setDaemon(true); 529 mRunnable = runnable; 530 mLogErrors = logErrors; 531 } 532 533 @Override 534 public void run() { 535 CommandStatus status; 536 try { 537 status = mRunnable.run() ? CommandStatus.SUCCESS : CommandStatus.FAILED; 538 } catch (InterruptedException e) { 539 CLog.i("runutil interrupted"); 540 status = CommandStatus.EXCEPTION; 541 } catch (Exception e) { 542 if (mLogErrors) { 543 CLog.e("Exception occurred when executing runnable"); 544 CLog.e(e); 545 } 546 status = CommandStatus.EXCEPTION; 547 } 548 synchronized (this) { 549 mStatus = status; 550 } 551 } 552 553 public void cancel() { 554 mRunnable.cancel(); 555 } 556 557 synchronized CommandStatus getStatus() { 558 return mStatus; 559 } 560 } 561 562 class RunnableResult implements IRunUtil.IRunnableResult { 563 private final ProcessBuilder mProcessBuilder; 564 private final CommandResult mCommandResult; 565 private final String mInput; 566 private Process mProcess = null; 567 private CountDownLatch mCountDown = null; 568 private Thread mExecutionThread; 569 private OutputStream stdOut = null; 570 private OutputStream stdErr = null; 571 private final boolean mCloseStreamAfterRun; 572 private Object mLock = new Object(); 573 private boolean mCancelled = false; 574 575 RunnableResult(final CommandResult result, final String input, 576 final ProcessBuilder processBuilder) { 577 this(result, input, processBuilder, null, null, false); 578 stdOut = new ByteArrayOutputStream(); 579 stdErr = new ByteArrayOutputStream(); 580 } 581 582 /** 583 * Alternative constructor that allows redirecting the output to any Outputstream. 584 * Stdout and stderr can be independently redirected to different Outputstream 585 * implementations. 586 * If streams are null, default behavior of using a buffer will be used. 587 */ 588 RunnableResult(final CommandResult result, final String input, 589 final ProcessBuilder processBuilder, OutputStream stdoutStream, 590 OutputStream stderrStream, boolean closeStreamAfterRun) { 591 mCloseStreamAfterRun = closeStreamAfterRun; 592 mProcessBuilder = processBuilder; 593 mInput = input; 594 mCommandResult = result; 595 // Ensure the outputs are never null 596 mCommandResult.setStdout(""); 597 mCommandResult.setStderr(""); 598 mCountDown = new CountDownLatch(1); 599 // Redirect IO, so that the outputstream for the spawn process does not fill up 600 // and cause deadlock. 601 if (stdoutStream != null) { 602 stdOut = stdoutStream; 603 } else { 604 stdOut = new ByteArrayOutputStream(); 605 } 606 if (stderrStream != null) { 607 stdErr = stderrStream; 608 } else { 609 stdErr = new ByteArrayOutputStream(); 610 } 611 } 612 613 /** Start a {@link Process} based on the {@link ProcessBuilder}. */ 614 @VisibleForTesting 615 Process startProcess() throws IOException { 616 return mProcessBuilder.start(); 617 } 618 619 @Override 620 public boolean run() throws Exception { 621 Thread stdoutThread = null; 622 Thread stderrThread = null; 623 synchronized (mLock) { 624 if (mCancelled == true) { 625 // if cancel() was called before run() took the lock, we do not even attempt 626 // to run. 627 return false; 628 } 629 mExecutionThread = Thread.currentThread(); 630 CLog.d("Running %s", mProcessBuilder.command()); 631 mProcess = startProcess(); 632 if (mInput != null) { 633 BufferedOutputStream processStdin = 634 new BufferedOutputStream(mProcess.getOutputStream()); 635 processStdin.write(mInput.getBytes("UTF-8")); 636 processStdin.flush(); 637 processStdin.close(); 638 } 639 // Log the command for thread tracking purpose. 640 stdoutThread = 641 inheritIO( 642 mProcess.getInputStream(), 643 stdOut, 644 String.format("inheritio-stdout-%s", mProcessBuilder.command())); 645 stderrThread = 646 inheritIO( 647 mProcess.getErrorStream(), 648 stdErr, 649 String.format("inheritio-stderr-%s", mProcessBuilder.command())); 650 } 651 // Wait for process to complete. 652 int rc = Integer.MIN_VALUE; 653 try { 654 try { 655 rc = mProcess.waitFor(); 656 // wait for stdout and stderr to be read 657 stdoutThread.join(IO_THREAD_JOIN_INTERVAL); 658 if (stdoutThread.isAlive()) { 659 CLog.d("stdout read thread %s still alive.", stdoutThread.toString()); 660 } 661 stderrThread.join(IO_THREAD_JOIN_INTERVAL); 662 if (stderrThread.isAlive()) { 663 CLog.d("stderr read thread %s still alive.", stderrThread.toString()); 664 } 665 // close the buffer that holds stdout/err content if default stream 666 // stream specified by caller should be handled by the caller. 667 if (mCloseStreamAfterRun) { 668 stdOut.close(); 669 stdErr.close(); 670 } 671 } finally { 672 // Write out the streams to the result. 673 if (stdOut instanceof ByteArrayOutputStream) { 674 mCommandResult.setStdout(((ByteArrayOutputStream)stdOut).toString("UTF-8")); 675 } else { 676 mCommandResult.setStdout("redirected to " + 677 stdOut.getClass().getSimpleName()); 678 } 679 if (stdErr instanceof ByteArrayOutputStream) { 680 mCommandResult.setStderr(((ByteArrayOutputStream)stdErr).toString("UTF-8")); 681 } else { 682 mCommandResult.setStderr("redirected to " + 683 stdErr.getClass().getSimpleName()); 684 } 685 } 686 } finally { 687 mCountDown.countDown(); 688 } 689 690 if (rc == 0) { 691 return true; 692 } else { 693 CLog.d("%s command failed. return code %d", mProcessBuilder.command(), rc); 694 } 695 return false; 696 } 697 698 @Override 699 public void cancel() { 700 mCancelled = true; 701 synchronized (mLock) { 702 if (mProcess == null) { 703 return; 704 } 705 CLog.i("Cancelling the process execution"); 706 mProcess.destroy(); 707 try { 708 // Only allow to continue if the Stdout has been read 709 // RunnableNotifier#Interrupt is the next call and will terminate the thread 710 if (!mCountDown.await(PROCESS_DESTROY_TIMEOUT_SEC, TimeUnit.SECONDS)) { 711 CLog.i("Process still not terminated, interrupting the execution thread"); 712 mExecutionThread.interrupt(); 713 mCountDown.await(); 714 } 715 } catch (InterruptedException e) { 716 CLog.i("interrupted while waiting for process output to be saved"); 717 } 718 } 719 } 720 } 721 722 /** 723 * Helper method to redirect input stream. 724 * 725 * @param src {@link InputStream} to inherit/redirect from 726 * @param dest {@link BufferedOutputStream} to inherit/redirect to 727 * @param name the name of the thread returned. 728 * @return a {@link Thread} started that receives the IO. 729 */ 730 private static Thread inheritIO(final InputStream src, final OutputStream dest, String name) { 731 Thread t = new Thread(new Runnable() { 732 @Override 733 public void run() { 734 try { 735 StreamUtil.copyStreams(src, dest); 736 } catch (IOException e) { 737 CLog.e("Failed to read input stream."); 738 } 739 } 740 }); 741 t.setName(name); 742 t.start(); 743 return t; 744 } 745 746 /** Allow to stop the Timer Thread for the run util instance if started. */ 747 @VisibleForTesting 748 void terminateTimer() { 749 if (mWatchdogInterrupt.get() != null) { 750 mWatchdogInterrupt.get().purge(); 751 mWatchdogInterrupt.get().cancel(); 752 } 753 } 754 755 /** Timer that will execute a interrupt on the Thread registered. */ 756 private class InterruptTask extends TimerTask { 757 758 private Thread mToInterrupt = null; 759 760 public InterruptTask(Thread t) { 761 mToInterrupt = t; 762 } 763 764 @Override 765 public void run() { 766 if (mToInterrupt != null) { 767 CLog.e("Interrupting with TimerTask"); 768 mInterruptibleGlobal = true; 769 mToInterrupt.interrupt(); 770 } 771 } 772 } 773 774 /** 775 * {@inheritDoc} 776 */ 777 @Override 778 public void setEnvVariablePriority(EnvPriority priority) { 779 if (this.equals(sDefaultInstance)) { 780 throw new UnsupportedOperationException("Cannot setWorkingDir on default RunUtil"); 781 } 782 mEnvVariablePriority = priority; 783 } 784 } 785