View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
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   * Utility to wrap access to JDK specific Operating system Mbean attributes.
67   *
68   * @author Zoltan Farkas
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    * Process execution utility.
187    * @param <T> type the stdout is reduced to.
188    * @param <E> type stderr is reduced to.
189    * @param command the command to execute.
190    * @param handler handler for child stdin, stdout and stderr. stdout and stderr handling will be done in 2 threads
191    * from the DefaultExecutor thread pool. while stdin handling will execute in the current thread.
192    * @param timeoutMillis time to wait for the process to execute.
193    * @param terminationTimeoutMillis this is the timeout used when trying to terminate the process gracefully.
194    * @return the response (respCode, stdout reduction, stderr reduction)
195    * @throws IOException
196    * @throws InterruptedException
197    * @throws ExecutionException
198    * @throws TimeoutException when timeout happens.
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) { // Executor might Reject
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) { // Executor might Reject
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") // best I can come up with so far. (same as JDK)
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)); //poll sleep.
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 }