Runtime.java
/*
* Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Additionally licensed with:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spf4j.base;
import org.spf4j.os.OperatingSystem;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.spf4j.concurrent.DefaultExecutor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import org.spf4j.base.avro.ApplicationInfo;
import org.spf4j.base.avro.Organization;
import org.spf4j.concurrent.UIDGenerator;
import org.spf4j.io.ByteArrayBuilder;
import org.spf4j.jmx.JmxExport;
import org.spf4j.jmx.Registry;
import org.spf4j.os.ProcessHandler;
import org.spf4j.os.ProcessResponse;
import org.spf4j.os.ProcessUtil;
import org.spf4j.recyclable.impl.ArraySuppliers;
import org.spf4j.unix.CLibrary;
import org.spf4j.unix.JVMArguments;
import org.spf4j.unix.Lsof;
import org.spf4j.unix.UnixRuntime;
/**
*
* @author zoly
*/
@SuppressFBWarnings("PATH_TRAVERSAL_IN")
public final class Runtime {
public static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
public static final long WAIT_FOR_SHUTDOWN_NANOS = TimeUnit.MILLISECONDS.toNanos(
Integer.getInteger("spf4j.waitForShutdownMillis", 30000));
public static final String TMP_FOLDER = System.getProperty("java.io.tmpdir");
public static final Path TMP_FOLDER_PATH = Paths.get(TMP_FOLDER);
public static final String JAVA_VERSION = System.getProperty("java.version");
public static final String USER_NAME = System.getProperty("user.name");
public static final String USER_DIR = System.getProperty("user.dir");
public static final String USER_HOME = System.getProperty("user.home");
public static final String JAVA_HOME = System.getProperty("java.home");
/**
* Unix PID identifying your process in the OC image it is running.
*/
public static final int PID = ProcessUtil.getPid();
public static final String PROCESS_NAME;
public static final String OS_NAME = OperatingSystem.getOsName();
/**
* a unique ID for this JVM process.
* PID@HOSTNAME:HEXNR
*/
public static final String PROCESS_ID;
public static final int NR_PROCESSORS;
public static final Version JAVA_PLATFORM;
private static final SortedMap<Integer, Set<Runnable>> SHUTDOWN_HOOKS = new TreeMap<>();
private static final List<Class<?>> PRELOADED = new ArrayList<>(2);
private static final java.lang.Runtime JAVA_RUNTIME = java.lang.Runtime.getRuntime();
static {
// priming certain functionality to make sure it works when we need it (classes are already loaded).
try (PrintStream stream = new PrintStream(new ByteArrayBuilder(), false, "UTF-8")) {
RuntimeException rex = new RuntimeException("priming");
Throwables.writeTo(rex, stream, Throwables.PackageDetail.NONE);
Throwables.containsNonRecoverable(rex);
} catch (UnsupportedEncodingException ex) {
throw new ExceptionInInitializerError(ex);
}
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
final int availableProcessors = JAVA_RUNTIME.availableProcessors();
if (availableProcessors <= 0) {
error("Invalid number of processors " + availableProcessors
+ " defaulting to 1");
NR_PROCESSORS = 1;
} else {
NR_PROCESSORS = availableProcessors;
}
String mxBeanName = runtimeMxBean.getName();
PROCESS_NAME = System.getProperty("spf4j.processName", mxBeanName);
boolean useUIDGeneratorForJvmId = Boolean.getBoolean("spf4j.useUIDForProcessId");
PROCESS_ID = useUIDGeneratorForJvmId ? UIDGenerator.generateIdBase("J", '-').toString()
: mxBeanName + ':' + Long.toHexString((System.currentTimeMillis() - 1509741164184L) / 1000);
final boolean dumpNonDaemonThreadInfoOnShutdown = Boolean.getBoolean("spf4j.dumpNonDaemonThreadInfoOnShutdown");
if (dumpNonDaemonThreadInfoOnShutdown) { // prime class...
PRELOADED.add(Threads.class);
}
JAVA_RUNTIME.addShutdownHook(new Thread(new ShutdownRunnable(false, dumpNonDaemonThreadInfoOnShutdown),
"spf4j queued shutdown"));
JAVA_PLATFORM = Version.fromSpecVersion(JAVA_VERSION);
if (Boolean.getBoolean("spf4j.runtime.jmx")) {
Registry.export(Jmx.class);
}
}
public enum Version {
V1_0, V1_1, V1_2, V1_3, V1_4, V1_5, V1_6, V1_7, V1_8, V1_9, V_10, V_11, V_12;
@SuppressFBWarnings("PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS") // not really redundant
public static Version fromSpecVersion(final String specVersion) {
String[] cmpnts = specVersion.split("\\.");
if (cmpnts.length > 1) {
return Version.values()[Integer.parseInt(cmpnts[1])];
} else if (cmpnts.length == 1) {
return Version.values()[Integer.parseInt(cmpnts[0])];
} else {
throw new IllegalArgumentException("Unsupported specVersion: " + specVersion);
}
}
}
private Runtime() {
}
public static org.spf4j.base.Version getAppVersion() {
return new org.spf4j.base.Version(getAppVersionString());
}
public static String getAppVersionString() {
Class<?> mainClass = getMainClass();
String version = mainClass.getPackage().getImplementationVersion();
if (version == null) {
return "N/A";
} else {
return version;
}
}
/**
* Returns application information.
* Information is retrieved from the app jar manifest.
* Manifest can be generated with maven like:
*
* <pre>
* {@code
* <plugin>
* <groupId>org.apache.maven.plugins</groupId>
* <artifactId>maven-jar-plugin</artifactId>
* <version>3.1.1</version>
* <configuration>
* <archive>
* <index>true</index>
* <manifest>
* <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
* <addClasspath>true</addClasspath>
* <classpathPrefix>lib/</classpathPrefix>
* <mainClass>org.spf4j.demo.Main</mainClass>
* </manifest>
* <manifestEntries>
* <Implementation-Vendor>${project.groupId}</Implementation-Vendor>
* <Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
* <Implementation-Title>${project.artifactId}</Implementation-Title>
* <Implementation-Version>${project.version}</Implementation-Version>
* <Implementation-Description>${project.description}</Implementation-Description>
* <Implementation-Url>${project.url}</Implementation-Url>
* <Implementation-Org>${project.organization.name}</Implementation-Org>
* <Implementation-Org-Url>${project.organization.url}</Implementation-Org-Url>
* <Implementation-Build>${buildNumber}</Implementation-Build>
* <Build-Time>${maven.build.timestamp}</Build-Time>
* </manifestEntries>
* </archive>
* </configuration>
* </plugin>
*
*
* }
* </pre>
* @return
*/
public static ApplicationInfo getApplicationInfo() {
Class<?> mainClass = getMainClass();
if (mainClass == null) {
try {
return new ApplicationInfo("N/A", "N/A main class", new URL("file://manifest/Implementation-Url"), null);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
Package p = mainClass.getPackage();
if (p == null) {
try {
return new ApplicationInfo("N/A", "N/A package Info", new URL("file://manifest/Implementation-Url"), null);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
URL jarSourceUrl = PackageInfo.getJarSourceUrl(mainClass);
String implementationTitle = p.getImplementationTitle();
if (implementationTitle == null) {
implementationTitle = "N/A manifest:Implementation-Title";
}
if (jarSourceUrl == null) {
try {
return new ApplicationInfo(implementationTitle, "N/A jar",
new URL("file://manifest/Implementation-Url"), null);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
try {
Manifest manifest = Reflections.getManifest(jarSourceUrl);
if (manifest == null) {
try {
return new ApplicationInfo(implementationTitle, "N/A jar manifest",
new URL("file://manifest/Implementation-Url"), null);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
Attributes mainAttributes = manifest.getMainAttributes();
String appDescription = mainAttributes.getValue("Implementation-Description");
String appUrl = mainAttributes.getValue("Implementation-Url");
String org = mainAttributes.getValue("Implementation-Org");
String orgUrl = mainAttributes.getValue("Implementation-Org-Url");
return new ApplicationInfo(implementationTitle, appDescription == null ? "" : appDescription,
(appUrl == null || appUrl.trim().isEmpty())
? new URL("file://manifest/Implementation-Url") : new URL(appUrl),
(org != null && !org.trim().isEmpty()) ? new Organization(org,
new URL((orgUrl == null || orgUrl.trim().isEmpty())
? "file://manifest/Implementation-Org-Url" : orgUrl)) : null);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public static boolean isShuttingDown() {
try {
java.lang.Runtime runtime = java.lang.Runtime.getRuntime();
Thread dummy = new Thread(AbstractRunnable.NOP);
runtime.addShutdownHook(dummy);
runtime.removeShutdownHook(dummy);
} catch (IllegalStateException e) {
return true;
}
return false;
}
@SuppressWarnings("checkstyle:regexp")
public static void error(final String message) {
System.err.println(message);
}
@SuppressWarnings("checkstyle:regexp")
public static void error(final String message, final Throwable t) {
System.err.println(message);
Throwables.writeTo(t, System.err, Throwables.PackageDetail.SHORT);
}
@SuppressWarnings("checkstyle:regexp")
public static void errorNoPackageDetail(final String message, final Throwable t) {
System.err.println(message);
Throwables.writeTo(t, System.err, Throwables.PackageDetail.NONE);
}
public static void goDownWithError(final SysExits exitCode) {
goDownWithError(null, exitCode.exitCode());
}
public static void goDownWithError(@Nullable final Throwable t, final SysExits exitCode) {
goDownWithError(t, exitCode.exitCode());
}
// Calling Halt is the only sensible thing to do when the JVM is hosed.
@SuppressFBWarnings("MDM_RUNTIME_EXIT_OR_HALT")
public static void goDownWithError(@Nullable final Throwable t, final int exitCode) {
try {
if (t != null) {
Throwables.writeTo(t, System.err, Throwables.PackageDetail.NONE); //High probability attempt to log first
error("Error, going down with exit code " + exitCode, t);
//Now we are pushing it...
Logger logger = Logger.getLogger(Runtime.class.getName());
logger.log(Level.SEVERE, "Error, going down with exit code {0}", exitCode);
logger.log(Level.SEVERE, "Exception detail", t);
} else {
error("Error, going down with exit code " + exitCode);
Logger.getLogger(Runtime.class.getName())
.log(Level.SEVERE, "Error, going down with exit code {0}", exitCode);
}
} finally {
JAVA_RUNTIME.halt(exitCode);
}
}
/**
* @return true on macosx.
* @deprecated use OperatingSystem
*/
@Deprecated
public static boolean isMacOsx() {
return OperatingSystem.isMacOsx();
}
/**
*
* @return true on windows.
* @deprecated use OperatingSystem
*/
@Deprecated
public static boolean isWindows() {
return OperatingSystem.isWindows();
}
public static boolean isTestFramework() {
StackTraceElement[][] stackTraces = Threads.getStackTraces(Threads.getThreads());
for (StackTraceElement[] sts : stackTraces) {
if (sts != null) {
for (StackTraceElement ste : sts) {
String className = ste.getClassName();
if (className.startsWith("org.junit") || className.startsWith("org.openjdk.jmh")) {
return true;
}
}
}
}
return false;
}
/**
* @return true if jna platform is present.
* @deprecated use JNA instead
*/
@Deprecated
public static boolean haveJnaPlatform() {
return JNA.haveJnaPlatform();
}
/**
* @return true if jna platform clib is present.
* @deprecated use JNA instead.
*/
@Deprecated
public static boolean haveJnaPlatformClib() {
return JNA.haveJnaPlatformClib();
}
/**
* get the number of open files by current java process.
*
* @return -1 if cannot get nr of open files
* @deprecated use OperatingSystem.getOpenFileDescriptorCount() instead
*/
@CheckReturnValue
@Deprecated
public static int getNrOpenFiles() {
return (int) OperatingSystem.getOpenFileDescriptorCount();
}
/**
* @deprecated use Lsof.getLsofOutput instead.
*/
@Nullable
@CheckReturnValue
@Deprecated
public static CharSequence getLsofOutput() {
return Lsof.getLsofOutput();
}
/**
* @deprecated use Processhandler
*/
@Deprecated
public interface ProcOutputHandler {
void handleStdOut(byte[] bytes, int length);
void stdOutDone();
void handleStdErr(byte[] bytes, int length);
void stdErrDone();
}
/**
* @deprecated use OperatingSystem.forkExec.
*/
@Deprecated
public static CharSequence run(final String[] command,
final long timeoutMillis) throws IOException, InterruptedException, ExecutionException, TimeoutException {
return OperatingSystem.forkExec(command, timeoutMillis);
}
/**
* @deprecated use OperatingSystem.killProcess.
*/
@Deprecated
public static int killProcess(final Process proc, final long terminateTimeoutMillis,
final long forceTerminateTimeoutMillis)
throws InterruptedException, TimeoutException {
return OperatingSystem.killProcess(proc, terminateTimeoutMillis, forceTerminateTimeoutMillis);
}
/**
* @deprecated use OperatingSystem.forkExec instead.
*/
public static int run(final String[] command, final ProcOutputHandler handler,
final long timeoutMillis)
throws IOException, InterruptedException, ExecutionException, TimeoutException {
return run(command, handler, timeoutMillis, 60000);
}
/**
* @deprecated use OperatingSystem.forkExec instead.
*/
@SuppressFBWarnings("COMMAND_INJECTION")
@Deprecated
public static int run(final String[] command, final ProcOutputHandler handler,
final long timeoutMillis, final long terminationTimeoutMillis)
throws IOException, InterruptedException, ExecutionException, TimeoutException {
ProcessResponse<Void, Void> resp = OperatingSystem.forkExec(command,
new ProcessHandler<Void, Void>() {
@Override
public Void handleStdOut(final InputStream is) throws IOException {
int cos;
byte[] buffer = ArraySuppliers.Bytes.TL_SUPPLIER.get(8192);
try {
while ((cos = is.read(buffer)) >= 0) {
handler.handleStdOut(buffer, cos);
}
} finally {
ArraySuppliers.Bytes.TL_SUPPLIER.recycle(buffer);
handler.stdOutDone();
}
return null;
}
@Override
public Void handleStdErr(final InputStream stderr) throws IOException {
int cos;
byte[] buffer = ArraySuppliers.Bytes.TL_SUPPLIER.get(8192);
try {
while ((cos = stderr.read(buffer)) >= 0) {
handler.handleStdErr(buffer, cos);
}
} finally {
ArraySuppliers.Bytes.TL_SUPPLIER.recycle(buffer);
handler.stdErrDone();
}
return null;
}
}, timeoutMillis, terminationTimeoutMillis);
return resp.getResponseCode();
}
public static void queueHookAtBeginning(final Runnable runnable) {
synchronized (SHUTDOWN_HOOKS) {
queueHook(Integer.MIN_VALUE, runnable);
}
}
public static void queueHookAtEnd(final Runnable runnable) {
queueHook(Integer.MAX_VALUE, runnable);
}
public static void queueHook(final int priority, final Runnable runnable) {
synchronized (SHUTDOWN_HOOKS) {
Integer pr = priority;
Set<Runnable> runnables = SHUTDOWN_HOOKS.get(pr);
if (runnables == null) {
runnables = new HashSet<>();
SHUTDOWN_HOOKS.put(pr, runnables);
}
runnables.add(runnable);
}
}
public static boolean removeQueuedShutdownHook(final Runnable runnable) {
if ("spf4j queued shutdown".equals(Thread.currentThread().getName())) {
return false;
}
synchronized (SHUTDOWN_HOOKS) {
for (Set<Runnable> entry : SHUTDOWN_HOOKS.values()) {
if (entry.remove(runnable)) {
return true;
}
}
}
return false;
}
/**
* @return returns the deadline as millis since epoch.
* @deprecated see ExecutionContexts.
*/
@Deprecated
public static long getDeadline() {
return Timing.getCurrentTiming().fromNanoTimeToEpochMillis(ExecutionContexts.getContextDeadlineNanos());
}
/**
* @return milliseconds until deadline.
* @deprecated see ExecutionContexts.
*/
@Deprecated
public static long millisToDeadline() throws TimeoutException {
return ExecutionContexts.getTimeToDeadline(TimeUnit.MILLISECONDS);
}
/**
* Attempts to run the GC in a verifiable way.
*
* @param timeoutMillis - timeout for GC attempt
* @return true if GC executed for sure, false otherwise, gc might have been executed though, but we cannot be sure.
*/
@SuppressFBWarnings
public static boolean gc(final long timeoutMillis) {
WeakReference<Object> ref = new WeakReference<>(new Object());
long deadline = TimeSource.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMillis);
do {
System.gc();
} while (ref.get() != null && TimeSource.nanoTime() < deadline);
return ref.get() == null;
}
public static CharSequence jrun(final Class<?> classWithMain,
final long timeoutMillis, final String... arguments)
throws IOException, InterruptedException, ExecutionException, TimeoutException {
final String classPath = ManagementFactory.getRuntimeMXBean().getClassPath();
return jrun(classWithMain, classPath, timeoutMillis, arguments);
}
public static CharSequence jrun(final Class<?> classWithMain, final String classPath, final long timeoutMillis,
final String... arguments) throws InterruptedException, ExecutionException, TimeoutException, IOException {
String[] arr = getJvmArgsNoJMXNoDebug();
return jrun(classWithMain, classPath, timeoutMillis, arr, arguments);
}
private static String[] getJvmArgsNoJMXNoDebug() {
List<String> jvmInputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
String[] arr;
if (jvmInputArgs.isEmpty()) {
arr = Arrays.EMPTY_STRING_ARRAY;
} else {
JVMArguments inputArguments = new JVMArguments(jvmInputArgs);
inputArguments.removeAllSystemPropertiesStartingWith("com.sun.management.jmxremote");
inputArguments.removeVMArgumentStartingWith("-agentlib:jdwp");
arr = inputArguments.toArray();
}
return arr;
}
public static synchronized void setCurrentDir(final String sourceAbsolutePath) {
if (haveJnaPlatformClib()) {
CLibrary.INSTANCE.chdir(sourceAbsolutePath);
}
System.setProperty("user.dir", sourceAbsolutePath);
}
public static synchronized String getCurrentDir() {
return System.getProperty("user.dir");
}
public static CharSequence jrun(final Class<?> classWithMain, final String classPath, final long timeoutMillis,
final String[] jvmArgs,
final String... arguments) throws InterruptedException, ExecutionException, TimeoutException, IOException {
final String jvmPath = JAVA_HOME + File.separatorChar + "bin" + File.separatorChar + "java";
String[] command = Arrays.concat(new String[]{jvmPath},
jvmArgs,
new String[]{"-cp", classPath, classWithMain.getName()},
arguments);
return OperatingSystem.forkExec(command, timeoutMillis);
}
public static void jrunAndLog(final Class<?> classWithMain,
final long timeoutMillis, final String... arguments)
throws IOException, InterruptedException, ExecutionException, TimeoutException {
final String classPath = ManagementFactory.getRuntimeMXBean().getClassPath();
jrunAndLog(classWithMain, classPath, timeoutMillis, arguments);
}
public static void jrunAndLog(final Class<?> classWithMain, final String classPath, final long timeoutMillis,
final String... arguments) throws InterruptedException, ExecutionException, TimeoutException, IOException {
String[] arr = getJvmArgsNoJMXNoDebug();
jrun(classWithMain, classPath, timeoutMillis, arr, arguments);
}
public static void jrunAndLog(final Class<?> classWithMain, final String classPath, final long timeoutMillis,
final String[] jvmArgs,
final String... arguments) throws InterruptedException, ExecutionException, TimeoutException, IOException {
final String jvmPath = JAVA_HOME + File.separatorChar + "bin" + File.separatorChar + "java";
String[] command = Arrays.concat(new String[]{jvmPath},
jvmArgs,
new String[]{"-cp", classPath, classWithMain.getName()},
arguments);
OperatingSystem.forkExecLog(command, timeoutMillis);
}
/**
* get the main Thread.
* @return null if there is no main thread (can happen when calling this is a shutdown hook)
*/
@Nullable
public static Thread getMainThread() {
Thread[] threads = Threads.getThreads();
for (Thread t : threads) {
if (t.getId() == 1L) {
return t;
}
}
return null;
}
/**
* Method will figure out the main class and cache the result for successive invocations.
* You should call this method prior to the main thread's termination.
* @return null if main class cannot be found.
*/
@Nullable
public static Class<?> getMainClass() {
return LazyMain.MAIN_CLASS;
}
private static class LazyMain {
private static final Class<?> MAIN_CLASS = getMainClass();
@Nullable
private static Class<?> getMainClass() {
Thread mainThread = getMainThread();
if (mainThread == null) {
return null;
}
StackTraceElement[] stackTrace = mainThread.getStackTrace();
if (stackTrace.length == 0) {
return null;
}
String className = stackTrace[stackTrace.length - 1].getClassName();
try {
return Class.forName(className);
} catch (ClassNotFoundException ex) {
NoClassDefFoundError tex = new NoClassDefFoundError("Cannot find " + className);
tex.initCause(ex);
throw tex;
}
}
}
public static final class Jmx {
@JmxExport
public static org.spf4j.base.avro.PackageInfo getPackageInfo(@JmxExport("className") final String className) {
return PackageInfo.getPackageInfo(className);
}
@JmxExport
public static void restart() throws IOException {
UnixRuntime.restart();
}
}
private static class ShutdownRunnable extends AbstractRunnable {
private final boolean dumpNonDaemonThreadInfoOnShutdown;
ShutdownRunnable(final boolean lenient, final boolean dumpNonDaemonThreadInfoOnShutdown) {
super(lenient);
this.dumpNonDaemonThreadInfoOnShutdown = dumpNonDaemonThreadInfoOnShutdown;
}
@Override
public void doRun() throws Exception {
Exception rex = null;
SortedMap<Integer, Set<Runnable>> hooks;
synchronized (SHUTDOWN_HOOKS) {
hooks = new TreeMap<>(SHUTDOWN_HOOKS);
for (Map.Entry<Integer, Set<Runnable>> entry : hooks.entrySet()) {
entry.setValue(new HashSet<>(entry.getValue()));
}
}
for (Map.Entry<Integer, Set<Runnable>> runnables : hooks.entrySet()) {
final Set<Runnable> values = runnables.getValue();
if (values.size() <= 1) {
for (Runnable runnable : values) {
try {
runnable.run();
} catch (RuntimeException ex) {
if (rex == null) {
rex = ex;
} else {
rex.addSuppressed(ex);
}
}
}
} else if (((int) runnables.getKey()) >= Integer.MAX_VALUE) {
Thread[] threads = new Thread[values.size()];
int i = 0;
for (Runnable runnable : values) {
Thread thread = new Thread(runnable);
thread.start();
threads[i++] = thread;
}
long deadline = TimeSource.nanoTime() + WAIT_FOR_SHUTDOWN_NANOS;
for (Thread thread : threads) {
try {
thread.join(TimeUnit.NANOSECONDS.toMillis(deadline - TimeSource.nanoTime()));
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
if (rex == null) {
rex = ex;
} else {
rex.addSuppressed(ex);
}
break;
}
}
} else {
List<Future<?>> futures = new ArrayList<>(values.size());
for (Runnable runnable : values) {
futures.add(DefaultExecutor.INSTANCE.submit(runnable));
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
if (rex == null) {
rex = ex;
} else {
rex.addSuppressed(ex);
}
break;
} catch (ExecutionException | RuntimeException ex) {
if (rex == null) {
rex = ex;
} else {
rex.addSuppressed(ex);
}
}
}
}
}
// print out info on all remaining non daemon threads.
if (dumpNonDaemonThreadInfoOnShutdown) {
Thread[] threads = Threads.getThreads();
Thread current = Thread.currentThread();
boolean first = true;
for (Thread thread : threads) {
if (thread.isAlive() && !thread.isDaemon() && !thread.equals(current)
&& !thread.getName().contains("DestroyJavaVM")) {
if (first) {
error("Non daemon threads still running:");
first = false;
}
error("Non daemon thread " + thread + ", stackTrace = "
+ java.util.Arrays.toString(thread.getStackTrace()));
}
}
}
if (rex != null) {
throw rex;
}
}
}
}