View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.examples.stress;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  /**
25   * Utility methods for a {@link Process}.
26   */
27  final class ProcessUtils {
28      /** The timeout to wait for the process exit value in milliseconds. */
29      private static final long DEFAULT_TIMEOUT_MILLIS = 1000L;
30  
31      /** No public construction. */
32      private ProcessUtils() {}
33  
34      /**
35       * Check the executable exists and has execute permissions.
36       *
37       * @param executable The executable.
38       * @throws ApplicationException If the executable is invalid.
39       */
40      static void checkExecutable(File executable) {
41          if (!executable.exists() ||
42              !executable.canExecute()) {
43              throw new ApplicationException("Program is not executable: " + executable);
44          }
45      }
46  
47      /**
48       * Check the output directory exists and has write permissions.
49       *
50       * @param fileOutputPrefix The file output prefix.
51       * @throws ApplicationException If the output directory is invalid.
52       */
53      static void checkOutputDirectory(File fileOutputPrefix) {
54          final File reportDir = fileOutputPrefix.getAbsoluteFile().getParentFile();
55          if (!reportDir.exists() ||
56              !reportDir.isDirectory() ||
57              !reportDir.canWrite()) {
58              throw new ApplicationException("Invalid output directory: " + reportDir);
59          }
60      }
61  
62      /**
63       * Builds the command for the sub-process.
64       *
65       * @param executable The executable file.
66       * @param executableArguments The executable arguments.
67       * @return the command
68       * @throws ApplicationException If the executable path cannot be resolved
69       */
70      static List<String> buildSubProcessCommand(File executable,
71                                                 List<String> executableArguments) {
72          final ArrayList<String> command = new ArrayList<>();
73          try {
74              command.add(executable.getCanonicalPath());
75          } catch (final IOException ex) {
76              // Not expected to happen as the file has been tested to exist
77              throw new ApplicationException("Cannot resolve executable path: " + ex.getMessage(), ex);
78          }
79          command.addAll(executableArguments);
80          return command;
81      }
82  
83      /**
84       * Get the exit value from the process, waiting at most for 1 second, otherwise kill the process
85       * and return {@code null}.
86       *
87       * <p>This should be used when it is expected the process has completed.</p>
88       *
89       * @param process The process.
90       * @return The exit value.
91       * @see Process#destroy()
92       */
93      static Integer getExitValue(Process process) {
94          return getExitValue(process, DEFAULT_TIMEOUT_MILLIS);
95      }
96  
97      /**
98       * Get the exit value from the process, waiting at most for the given time, otherwise
99       * kill the process and return {@code null}.
100      *
101      * <p>This should be used when it is expected the process has completed. If the timeout
102      * expires an error message is logged before the process is killed.</p>
103      *
104      * @param process The process.
105      * @param timeoutMillis The timeout (in milliseconds).
106      * @return The exit value.
107      * @see Process#destroy()
108      */
109     static Integer getExitValue(Process process, long timeoutMillis) {
110         final long startTime = System.currentTimeMillis();
111         long remaining = timeoutMillis;
112 
113         while (remaining > 0) {
114             try {
115                 return process.exitValue();
116             } catch (final IllegalThreadStateException ex) {
117                 try {
118                     Thread.sleep(Math.min(remaining + 1, 100));
119                 } catch (final InterruptedException e) {
120                     // Reset interrupted status and stop waiting
121                     Thread.currentThread().interrupt();
122                     break;
123                 }
124             }
125             remaining = timeoutMillis - (System.currentTimeMillis() - startTime);
126         }
127 
128         LogUtils.error("Failed to obtain exit value after %d ms, forcing termination", timeoutMillis);
129 
130         // Not finished so kill it
131         process.destroy();
132 
133         return null;
134     }
135 }