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     /**
    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