1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.os;
33
34 import com.google.common.util.concurrent.MoreExecutors;
35 import com.sun.management.UnixOperatingSystemMXBean;
36 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.lang.management.ManagementFactory;
41 import java.lang.management.OperatingSystemMXBean;
42 import java.util.Arrays;
43 import java.util.Map;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Future;
47 import java.util.concurrent.SynchronousQueue;
48 import java.util.concurrent.ThreadPoolExecutor;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.TimeoutException;
51 import java.util.concurrent.atomic.AtomicReference;
52 import java.util.logging.Level;
53 import javax.annotation.CheckReturnValue;
54 import javax.annotation.Nullable;
55 import org.spf4j.base.JNA;
56 import org.spf4j.base.Pair;
57 import org.spf4j.base.Throwables;
58 import org.spf4j.base.SysExits;
59 import org.spf4j.base.TimeSource;
60 import org.spf4j.concurrent.Futures;
61 import org.spf4j.io.csv.CharSeparatedValues;
62 import org.spf4j.unix.UnixException;
63 import org.spf4j.unix.UnixResources;
64
65
66
67
68
69
70 @SuppressFBWarnings({"FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY", "HES_EXECUTOR_NEVER_SHUTDOWN"})
71 public final class OperatingSystem {
72
73 private static final java.util.logging.Logger LOG =
74 java.util.logging.Logger.getLogger(OperatingSystem.class.getName());
75
76 private static final ExecutorService EXEC =
77 MoreExecutors.getExitingExecutorService(new ThreadPoolExecutor(0, Integer.MAX_VALUE,
78 60L, TimeUnit.SECONDS,
79 new SynchronousQueue<Runnable>()));
80
81 private static final long ABORT_TIMEOUT_MILLIS = Long.getLong("spf4j.os.abortTimeoutMillis", 5000);
82
83 private static final OperatingSystemMXBean OS_MBEAN;
84
85 private static final com.sun.management.OperatingSystemMXBean SUN_OS_MBEAN;
86
87 private static final UnixOperatingSystemMXBean UNIX_OS_MBEAN;
88
89 public static final long MAX_NR_OPENFILES;
90
91 private static final boolean IS_MAC_OSX;
92 private static final boolean IS_WINDOWS;
93 private static final String OS_NAME;
94
95 static {
96 final String osName = System.getProperty("os.name");
97 OS_NAME = osName;
98 IS_MAC_OSX = "Mac OS X".equals(osName);
99 IS_WINDOWS = osName.startsWith("Windows");
100 OS_MBEAN = ManagementFactory.getOperatingSystemMXBean();
101 if (OS_MBEAN instanceof com.sun.management.OperatingSystemMXBean) {
102 SUN_OS_MBEAN = (com.sun.management.OperatingSystemMXBean) OS_MBEAN;
103 } else {
104 SUN_OS_MBEAN = null;
105 }
106 if (OS_MBEAN instanceof UnixOperatingSystemMXBean) {
107 UNIX_OS_MBEAN = (UnixOperatingSystemMXBean) OS_MBEAN;
108 MAX_NR_OPENFILES = UNIX_OS_MBEAN.getMaxFileDescriptorCount();
109 } else {
110 UNIX_OS_MBEAN = null;
111 if (IS_WINDOWS) {
112 MAX_NR_OPENFILES = Integer.MAX_VALUE;
113 } else if (JNA.haveJnaPlatformClib()) {
114 try {
115 MAX_NR_OPENFILES = UnixResources.RLIMIT_NOFILE.getSoftLimit();
116 } catch (UnixException ex) {
117 throw new ExceptionInInitializerError(ex);
118 }
119 } else {
120 MAX_NR_OPENFILES = Integer.MAX_VALUE;
121 }
122 }
123 }
124
125 private OperatingSystem() {
126 }
127
128 public static boolean isMacOsx() {
129 return IS_MAC_OSX;
130 }
131
132 public static boolean isWindows() {
133 return IS_WINDOWS;
134 }
135
136 public static String getOsName() {
137 return OS_NAME;
138 }
139
140
141
142 public static OperatingSystemMXBean getOSMbean() {
143 return OS_MBEAN;
144 }
145
146 @Nullable
147 public static com.sun.management.OperatingSystemMXBean getSunJdkOSMBean() {
148 return SUN_OS_MBEAN;
149 }
150
151 @Nullable
152 public static UnixOperatingSystemMXBean getUnixOsMBean() {
153 return UNIX_OS_MBEAN;
154 }
155
156 public static long getOpenFileDescriptorCount() {
157 if (UNIX_OS_MBEAN != null) {
158 return UNIX_OS_MBEAN.getOpenFileDescriptorCount();
159 } else {
160 return -1;
161 }
162 }
163
164 public static long getMaxFileDescriptorCount() {
165 return MAX_NR_OPENFILES;
166 }
167
168 public static int killProcess(final Process proc, final long terminateTimeoutMillis,
169 final long forceTerminateTimeoutMillis)
170 throws InterruptedException, TimeoutException {
171
172 proc.destroy();
173 if (proc.waitFor(terminateTimeoutMillis, TimeUnit.MILLISECONDS)) {
174 return proc.exitValue();
175 } else {
176 proc.destroyForcibly();
177 if (!proc.waitFor(forceTerminateTimeoutMillis, TimeUnit.MILLISECONDS)) {
178 throw new TimeoutException("Cannot terminate " + proc);
179 } else {
180 return proc.exitValue();
181 }
182 }
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200 @SuppressFBWarnings({ "COMMAND_INJECTION", "CC_CYCLOMATIC_COMPLEXITY" })
201 public static <T, E> ProcessResponse<T, E> forkExec(final String[] command, final ProcessHandler<T, E> handler,
202 final long timeoutMillis, final long terminationTimeoutMillis)
203 throws IOException, InterruptedException, ExecutionException, TimeoutException {
204 LOG.log(Level.FINE, "Executing {0}", new Object() {
205 @Override
206 public String toString() {
207 return new CharSeparatedValues(' ').toCsvRowString((Object[]) command);
208 }
209 });
210 final Process proc = java.lang.Runtime.getRuntime().exec(command);
211 handler.started(proc);
212 try (InputStream pos = proc.getInputStream();
213 InputStream pes = proc.getErrorStream();
214 OutputStream pis = proc.getOutputStream()) {
215 Future<E> esh;
216 Future<T> osh;
217 final AtomicReference<Throwable> eex = new AtomicReference<>();
218 try {
219 esh = EXEC.submit(() -> {
220 try {
221 return handler.handleStdErr(pes);
222 } catch (Throwable t) {
223 eex.set(t);
224 throw t;
225 }
226 });
227 } catch (RuntimeException ex) {
228 int result = killProcess(proc, terminationTimeoutMillis, ABORT_TIMEOUT_MILLIS);
229 throw new ExecutionException("Cannot execute stderr handler, killed process returned " + result, ex);
230 }
231 try {
232 osh = EXEC.submit(() -> {
233 try {
234 return handler.handleStdOut(pos);
235 } catch (Throwable t) {
236 eex.set(t);
237 throw t;
238 }
239 });
240 } catch (RuntimeException ex) {
241 RuntimeException cex = Futures.cancelAll(true, esh);
242 if (cex != null) {
243 ex.addSuppressed(cex);
244 }
245 int result = killProcess(proc, terminationTimeoutMillis, ABORT_TIMEOUT_MILLIS);
246 throw new ExecutionException("Cannot execute stdout handler, killed process returned " + result, ex);
247 }
248 long deadlineNanos = TimeSource.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutMillis, TimeUnit.MILLISECONDS);
249 try {
250 handler.writeStdIn(pis);
251 } catch (RuntimeException | IOException ex) {
252 RuntimeException cex = Futures.cancelAll(true, esh, osh);
253 if (cex != null) {
254 ex.addSuppressed(cex);
255 }
256 int result = killProcess(proc, terminationTimeoutMillis, ABORT_TIMEOUT_MILLIS);
257 throw new ExecutionException("Failure executing stdin handler, killed process returned " + result, ex);
258 }
259 try {
260 int result = waitFor(eex, proc, timeoutMillis, TimeUnit.MILLISECONDS);
261 Pair<Map<Future, Object>, Exception> results = Futures.getAllWithDeadlineNanos(deadlineNanos, osh, esh);
262 Exception hex = results.getSecond();
263 Map<Future, Object> asyncRes = results.getFirst();
264 if (hex == null) {
265 return new ProcessResponse<>(result, (T) asyncRes.get(osh), (E) asyncRes.get(esh));
266 } else {
267 Throwables.throwException(hex);
268 throw new IllegalStateException();
269 }
270 } catch (TimeoutException | ExecutionException | InterruptedException ex) {
271 killProcess(proc, terminationTimeoutMillis, ABORT_TIMEOUT_MILLIS);
272 RuntimeException cex = Futures.cancelAll(true, osh, esh);
273 if (cex != null) {
274 ex.addSuppressed(cex);
275 }
276 throw ex;
277 }
278 }
279 }
280
281 @SuppressFBWarnings("MDM_THREAD_YIELD")
282 private static int waitFor(final AtomicReference<Throwable> exr,
283 final Process process,
284 final long timeout, final TimeUnit unit)
285 throws InterruptedException, ExecutionException, TimeoutException {
286 long startTime = TimeSource.nanoTime();
287 long timeoutNanos = unit.toNanos(timeout);
288 do {
289 try {
290 return process.exitValue();
291 } catch (IllegalThreadStateException ex) {
292 Throwable get = exr.get();
293 if (get != null) {
294 throw new ExecutionException(get);
295 }
296 long rem = timeoutNanos - (TimeSource.nanoTime() - startTime);
297 if (rem > 0) {
298 TimeUnit.NANOSECONDS.sleep(Math.min(rem, 100000000));
299 } else {
300 break;
301 }
302 }
303 } while (true);
304 throw new TimeoutException("Process " + process + " timed out after " + timeout + " " + unit);
305 }
306
307
308 @CheckReturnValue
309 public static String forkExec(final String[] command,
310 final long timeoutMillis) throws IOException, InterruptedException, ExecutionException, TimeoutException {
311 ProcessResponse<String, String> resp
312 = forkExec(command, new StdOutToStringProcessHandler(), timeoutMillis, 60000);
313 if (resp.getResponseExitCode() != SysExits.OK) {
314 throw new ExecutionException("Failed to execute " + Arrays.toString(command)
315 + ", exitCode = " + resp.getResponseCode() + ", stderr = " + resp.getErrOutput(), null);
316 }
317 return resp.getOutput();
318 }
319
320 public static void forkExecLog(final String[] command,
321 final long timeoutMillis) throws IOException, InterruptedException, ExecutionException, TimeoutException {
322 ProcessResponse<Void, Void> resp
323 = forkExec(command,
324 new LoggingProcessHandler(java.util.logging.Logger.getLogger("fork." + command[0])),
325 timeoutMillis, 60000);
326 if (resp.getResponseExitCode() != SysExits.OK) {
327 throw new ExecutionException("Failed to execute " + java.util.Arrays.toString(command)
328 + ", exitCode = " + resp.getResponseCode() + ", stderr = " + resp.getErrOutput(), null);
329 }
330 }
331
332 @SuppressFBWarnings("MRC_METHOD_RETURNS_CONSTANT")
333 public static String getHostName() {
334 return "127.0.1.1";
335 }
336
337 }