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