package com.oracle.svm.hosted;
import java.io.Closeable;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.option.SubstrateOptionsParser;
public class DeadlockWatchdog implements Closeable {
private final int watchdogInterval;
private final boolean watchdogExitOnTimeout;
private final Thread thread;
private volatile long nextDeadline;
private volatile boolean stopped;
DeadlockWatchdog() {
watchdogInterval = SubstrateOptions.DeadlockWatchdogInterval.getValue();
watchdogExitOnTimeout = SubstrateOptions.DeadlockWatchdogExitOnTimeout.getValue();
if (watchdogInterval > 0) {
thread = new Thread(this::watchdogThread);
thread.start();
} else {
thread = null;
}
}
public void recordActivity() {
nextDeadline = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(watchdogInterval);
}
@Override
public void close() {
stopped = true;
if (thread != null) {
thread.interrupt();
}
}
void watchdogThread() {
recordActivity();
while (!stopped) {
long now = System.currentTimeMillis();
if (now >= nextDeadline) {
System.err.println();
System.err.println("=== Image generator watchdog detected no activity. This can be a sign of a deadlock during image building. Dumping all stack traces. Current time: " + new Date());
threadDump();
Runtime runtime = Runtime.getRuntime();
final long heapSizeUnit = 1024 * 1024;
long usedHeapSize = runtime.totalMemory() / heapSizeUnit;
long freeHeapSize = runtime.freeMemory() / heapSizeUnit;
long maximumHeapSize = runtime.maxMemory() / heapSizeUnit;
System.err.printf("=== Memory statistics (in MB):%n=== Used heap size: %d%n=== Free heap size: %d%n=== Maximum heap size: %d%n", usedHeapSize, freeHeapSize, maximumHeapSize);
System.err.flush();
if (watchdogExitOnTimeout) {
System.err.println("=== Image generator watchdog is aborting image generation. To configure the watchdog, use the options " +
SubstrateOptionsParser.commandArgument(SubstrateOptions.DeadlockWatchdogInterval, Integer.toString(watchdogInterval), null) + " and " +
SubstrateOptionsParser.commandArgument(SubstrateOptions.DeadlockWatchdogExitOnTimeout, "+", null));
System.exit(1);
} else {
recordActivity();
}
}
try {
Thread.sleep(Math.min(nextDeadline - now, TimeUnit.SECONDS.toMillis(1)));
} catch (InterruptedException e) {
}
}
}
private static void threadDump() {
for (ThreadInfo ti : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
printThreadInfo(ti);
printLockInfo(ti.getLockedSynchronizers());
}
System.err.println();
}
private static void printThreadInfo(ThreadInfo ti) {
StringBuilder sb = new StringBuilder("\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState());
if (ti.getLockName() != null) {
sb.append(" on lock=" + ti.getLockName());
}
if (ti.isSuspended()) {
sb.append(" (suspended)");
}
if (ti.isInNative()) {
sb.append(" (running in native)");
}
System.err.println(sb.toString());
if (ti.getLockOwnerName() != null) {
System.err.println(" owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId());
}
StackTraceElement[] stacktrace = ti.getStackTrace();
MonitorInfo[] monitors = ti.getLockedMonitors();
for (int i = 0; i < stacktrace.length; i++) {
StackTraceElement ste = stacktrace[i];
System.err.println(" at " + ste.toString());
for (MonitorInfo mi : monitors) {
if (mi.getLockedStackDepth() == i) {
System.err.println(" - locked " + mi);
}
}
}
System.err.println();
}
private static void printLockInfo(LockInfo[] locks) {
if (locks.length > 0) {
System.err.println(" Locked synchronizers: count = " + locks.length);
for (LockInfo li : locks) {
System.err.println(" - " + li);
}
System.err.println();
}
}
}