Home | History | Annotate | Download | only in util
      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      * {@inheritDoc}
    316      */
    317     @Override
    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
    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
    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
    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      */
    435     long getCurrentTime() {
    436         return System.currentTimeMillis();
    437     }
    438 
    439     /**
    440      * {@inheritDoc}
    441      */
    442     @Override
    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
    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
    471     public boolean isInterruptAllowed() {
    472         return mIsInterruptAllowed.get();
    473     }
    474 
    475     /**
    476      * {@inheritDoc}
    477      */
    478     @Override
    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
    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 
    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 
    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
    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 
    560         public void cancel() {
    561             mRunnable.cancel();
    562         }
    563 
    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 
    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          */
    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
    622         Process startProcess() throws IOException {
    623             return mProcessBuilder.start();
    624         }
    625 
    626         @Override
    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
    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
    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      */
    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
    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 
    774         public InterruptTask(Thread t) {
    775             mToInterrupt = t;
    776         }
    777 
    778         @Override
    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
    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