
jdk.graal.compiler.core.CompilationWatchDog Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The GraalVM compiler and the Graal-truffle optimizer.
The newest version!
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.graal.compiler.core;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.graalvm.nativeimage.ImageInfo;
import jdk.graal.compiler.core.common.CompilationIdentifier;
import jdk.graal.compiler.core.common.util.Util;
import jdk.graal.compiler.debug.TTY;
import jdk.graal.compiler.options.Option;
import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionType;
import jdk.graal.compiler.options.OptionValues;
import jdk.graal.compiler.serviceprovider.GraalServices;
import jdk.graal.compiler.serviceprovider.IsolateUtil;
import jdk.vm.ci.common.NativeImageReinitialize;
/**
* A watch dog for {@linkplain #watch watching} and reporting on long running compilations.
*
* For each compilation, a watch dog task is scheduled with an initial delay of
* {@link Options#CompilationWatchDogStartDelay} seconds. Once a scheduled watch dog task starts and
* the compilation is still running, it will {@linkplain EventHandler#onLongCompilation report} a
* stack trace for the compilation. Further reports will continue until the compilation stops. The
* period between reports is doubled each time (i.e., at 1, 2, 4, 8 seconds etc).
*
* If {@code D ==} {@link Options#CompilationWatchDogVMExitDelay} and {@code D != 0} and the
* compilation appears to make no progress for {@code D} seconds (i.e., identical stack traces are
* seen for {@code D} seconds), it is {@linkplain EventHandler#onStuckCompilation reported} as
* stuck. It is expected most watch dog event handlers will exit the VM in this case. For this
* reason, the default value is 0 as VM exiting needs to be an opt-in behavior.
*
* The executor used to schedule and run the tasks uses a single thread that will terminate when
* compilation goes idle. The latter is important to also allow destroying a libgraal isolate when
* compilation goes idle.
*
* The above means only 1 task is run at a time. Most watch dog tasks are expected to be cancelled
* while waiting to be run. A problematic compilation will eventually be watched as the executor
* effectively sorts its tasks in the order they are received.
*/
public final class CompilationWatchDog implements Runnable, AutoCloseable {
/**
* An object whose {@link #toString()} method returns the current thread's name along with a
* {@code "@"} suffix if running in a Native Image where {@code } is the current isolate
* ID (e.g., {@code "WatchDog-2@1"}).
*/
public static final Object CURRENT_THREAD_LABEL = new Object() {
@Override
public String toString() {
String isolateID = IsolateUtil.getIsolateID(false);
if (!isolateID.isEmpty()) {
isolateID = "@" + isolateID;
}
return Thread.currentThread().getName() + isolateID;
}
};
/**
* Acts on compilation watch dog events.
*/
public interface EventHandler {
/**
* Exit code to be used when exiting a process due to a stuck compilation.
*/
int STUCK_COMPILATION_EXIT_CODE = 84;
/**
* Notifies this object that a compilation is long running.
*
* @param watchDog the watch dog watching the compilation
* @param watched the watched compiler thread
* @param compilation the compilation
* @param elapsed nanoseconds the compilation has been observed by the watch dog task which
* may be shorter that time since compilation started
* @param stackTrace snapshot stack trace for {@code watched}
*/
default void onLongCompilation(CompilationWatchDog watchDog, Thread watched, CompilationIdentifier compilation,
long elapsed, StackTraceElement[] stackTrace) {
TTY.printf("======================= WATCH DOG =======================%n" +
"%s: detected long running compilation %s [%.3f seconds]%n%s", CURRENT_THREAD_LABEL, compilation,
seconds(elapsed), Util.toString(stackTrace));
}
/**
* Notifies this object that a compilation appears to be stuck. The typical action to take
* is to exit the VM with exit code {@link #STUCK_COMPILATION_EXIT_CODE}.
*
* @param watchDog the watch dog watching the compilation
* @param watched the watched compiler thread
* @param compilation the compilation
* @param stackTrace snapshot stack trace for {@code watched}
* @param stuckTime seconds compilation appears to have been stuck for
*/
default void onStuckCompilation(CompilationWatchDog watchDog, Thread watched, CompilationIdentifier compilation,
StackTraceElement[] stackTrace, long stuckTime) {
TTY.printf("======================= WATCH DOG =======================%n" +
"%s: observed identical stack traces for %d seconds, indicating a stuck compilation %s%n%s%n", CURRENT_THREAD_LABEL,
stuckTime, compilation, Util.toString(stackTrace));
}
/**
* Notifies this object that {@code exception} occurred while watching a compilation.
*
* @param exception
*/
default void onException(Throwable exception) {
if (exception instanceof OutOfMemoryError) {
// Silently swallow an OOME. If memory is really limited, the compiler
// thread will hit it and take appropriate reporting/handling action.
} else {
exception.printStackTrace(TTY.out);
}
}
/**
* A shareable, default implementation of {@link EventHandler}.
*/
EventHandler DEFAULT = new EventHandler() {
};
}
public static class Options {
// @formatter:off
@Option(help = "Delay in seconds before watch dog monitors a compilation (0 disables monitoring).", type = OptionType.Debug)
public static final OptionKey CompilationWatchDogStartDelay = new OptionKey<>(0);
@Option(help = "Number of seconds after which a compilation appearing to make no progress causes the VM to exit " +
"(0 disables VM exiting).", type = OptionType.Debug)
public static final OptionKey CompilationWatchDogVMExitDelay = new OptionKey<>(0);
// @formatter:on
}
private final Thread watchedThread;
/**
* Support for handling a watch event.
*/
private final EventHandler eventHandler;
/**
* @see Options#CompilationWatchDogVMExitDelay
*/
private final long vmExitDelayNS;
/**
* Object representing the compilation being watched. It is volatile as it is used to
* communicate when the compilation is finished.
*/
private volatile CompilationIdentifier compilation;
private StackTraceElement[] lastStackTrace;
private final ScheduledExecutorService singleShotExecutor;
CompilationWatchDog(CompilationIdentifier compilation, Thread watchedThread, int delay, int vmExitDelay,
boolean singleShotExecutor, EventHandler eventHandler) {
this.compilation = compilation;
this.watchedThread = watchedThread;
this.vmExitDelayNS = TimeUnit.SECONDS.toNanos(vmExitDelay);
this.eventHandler = eventHandler == null ? EventHandler.DEFAULT : eventHandler;
trace("started compiling %s", compilation);
if (singleShotExecutor) {
this.singleShotExecutor = createExecutor();
this.task = this.singleShotExecutor.schedule(this, delay, TimeUnit.SECONDS);
} else {
this.singleShotExecutor = null;
this.task = schedule(this, delay);
}
}
private void stopCompilation() {
trace("stopped compiling %s", compilation);
this.compilation = null;
this.task.cancel(true);
if (singleShotExecutor != null) {
singleShotExecutor.shutdownNow();
while (!singleShotExecutor.isTerminated()) {
try {
singleShotExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
}
}
/**
* Saves the current stack trace {@link StackTraceElement} of the monitored compiler thread
* {@link CompilationWatchDog#watchedThread}.
*
* @param newStackTrace the current stack trace of the monitored compiler thread
* @return {@code true} if the stack trace is not equal to the last stack trace or it is the
* first one, {@code false} otherwise.
*/
private boolean recordStackTrace(StackTraceElement[] newStackTrace) {
if (lastStackTrace == null || !Arrays.equals(lastStackTrace, newStackTrace)) {
lastStackTrace = newStackTrace;
return true;
}
return false;
}
private final boolean debug = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.CompilationWatchDog"));
/**
* Handle to scheduled task.
*/
private final ScheduledFuture> task;
private void trace(String format, Object... args) {
if (debug) {
TTY.println(CURRENT_THREAD_LABEL + ": " + String.format(format, args));
}
}
private static double seconds(long ns) {
return (double) ns / TimeUnit.SECONDS.toNanos(1);
}
@Override
public String toString() {
return "WatchDog[" + watchedThread.getName() + "]";
}
@Override
public void run() {
try {
CompilationIdentifier comp = compilation;
long startNS = System.nanoTime();
long elapsedNS = 0;
long reportDelayNS = TimeUnit.SECONDS.toNanos(1);
long nextReportNS = startNS;
long lastUniqueStackTraceNS = startNS;
while (compilation != null) {
long currentNS = System.nanoTime();
comp = compilation;
trace("took a stack trace [%.3f seconds]", seconds(elapsedNS));
boolean uniqueStackTrace = recordStackTrace(watchedThread.getStackTrace());
if (uniqueStackTrace) {
lastUniqueStackTraceNS = currentNS;
}
long stuckTime = currentNS - lastUniqueStackTraceNS;
if (vmExitDelayNS != 0 && stuckTime >= vmExitDelayNS) {
eventHandler.onStuckCompilation(this, watchedThread, comp, lastStackTrace, TimeUnit.NANOSECONDS.toSeconds(stuckTime));
} else if (uniqueStackTrace) {
if (currentNS >= nextReportNS) {
eventHandler.onLongCompilation(this, watchedThread, comp, elapsedNS, lastStackTrace);
nextReportNS = currentNS + reportDelayNS;
reportDelayNS <<= 1;
}
}
try {
Thread.sleep(1000);
elapsedNS = System.nanoTime() - startNS;
} catch (InterruptedException e) {
elapsedNS = System.nanoTime() - startNS;
trace("interrupted [%.3f seconds]", seconds(elapsedNS));
}
}
trace("stopped watching %s [%.3f seconds]", comp, seconds(elapsedNS));
} catch (Throwable t) {
eventHandler.onException(t);
}
}
@NativeImageReinitialize private static ScheduledExecutorService watchDogService;
private static synchronized ScheduledFuture> schedule(CompilationWatchDog watchdog, int delay) {
if (watchDogService == null) {
watchDogService = createExecutor();
}
return watchDogService.schedule(watchdog, delay, TimeUnit.SECONDS);
}
private static ScheduledExecutorService createExecutor() {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new GraalServiceThread(CompilationWatchDog.class.getSimpleName(), r);
thread.setName("WatchDog-" + GraalServices.getThreadId(thread));
thread.setPriority(Thread.MAX_PRIORITY);
thread.setDaemon(true);
return thread;
}
};
int poolSize = 1;
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(poolSize, threadFactory);
executor.setRemoveOnCancelPolicy(true);
executor.allowCoreThreadTimeOut(true);
return executor;
}
/**
* Opens a scope for watching a compilation.
*
* @param compilation identifies the compilation being watched
* @param singleShotExecutor if true, then a dedicated executor is created for this task and it
* is shutdown once the compilation ends
* @param eventHandler notified of events like a compilation running long running or getting
* stuck. If {@code null}, {@link EventHandler#DEFAULT} is used.
* @return {@code null} if the compilation watch dog is disabled otherwise a new
* {@link CompilationWatchDog} object. The returned value should be used in a
* {@code try}-with-resources statement whose scope is the whole compilation so that
* leaving the scope will cause {@link #close()} to be called.
*/
public static CompilationWatchDog watch(CompilationIdentifier compilation, OptionValues options, boolean singleShotExecutor, EventHandler eventHandler) {
int delay = Options.CompilationWatchDogStartDelay.getValue(options);
if (ImageInfo.inImageBuildtimeCode() && !Options.CompilationWatchDogStartDelay.hasBeenSet(options)) {
// Disable watch dog by default when building a native image
delay = 0;
}
if (delay > 0) {
Thread watchedThread = Thread.currentThread();
int vmExitDelay = Options.CompilationWatchDogVMExitDelay.getValue(options);
CompilationWatchDog watchDog = new CompilationWatchDog(compilation, watchedThread, delay,
vmExitDelay, singleShotExecutor, eventHandler);
return watchDog;
}
return null;
}
@Override
public void close() {
stopCompilation();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy