Skip to content

Commit 43343c9

Browse files
author
Christian Wimmer
committed
Shutdown hooks must run after joining non-daemon threads
1 parent dc37c9a commit 43343c9

File tree

2 files changed

+70
-30
lines changed

2 files changed

+70
-30
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java

+39-28
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import static com.oracle.svm.core.option.SubstrateOptionsParser.BooleanOptionFormat.NAME_VALUE;
3030
import static com.oracle.svm.core.option.SubstrateOptionsParser.BooleanOptionFormat.PLUS_MINUS;
3131

32+
import java.lang.invoke.MethodHandle;
33+
import java.lang.invoke.MethodHandles;
3234
//Checkstyle: allow reflection
3335
import java.lang.reflect.Method;
3436
import java.util.ArrayList;
@@ -85,21 +87,18 @@ public class JavaMainWrapper {
8587

8688
public static class JavaMainSupport {
8789

88-
private final Method javaMainMethod;
90+
final MethodHandle javaMainHandle;
91+
final String javaMainClassName;
8992

9093
@Platforms(Platform.HOSTED_ONLY.class)
91-
public JavaMainSupport(Method javaMainMethod) {
92-
this.javaMainMethod = javaMainMethod;
93-
}
94-
95-
private Method getJavaMainMethod() {
96-
assert javaMainMethod != null;
97-
return javaMainMethod;
94+
public JavaMainSupport(Method javaMainMethod) throws IllegalAccessException {
95+
this.javaMainHandle = MethodHandles.lookup().unreflect(javaMainMethod);
96+
this.javaMainClassName = javaMainMethod.getDeclaringClass().getName();
9897
}
9998

10099
public String getJavaCommand() {
101-
if (javaMainMethod != null && mainArgs != null) {
102-
StringBuilder commandLine = new StringBuilder(javaMainMethod.getDeclaringClass().getName());
100+
if (mainArgs != null) {
101+
StringBuilder commandLine = new StringBuilder(javaMainClassName);
103102

104103
for (String arg : mainArgs) {
105104
commandLine.append(' ');
@@ -160,28 +159,40 @@ public static int run(int paramArgc, CCharPointerPointer paramArgv) throws Excep
160159
args = RuntimePropertyParser.parse(args);
161160
}
162161
mainArgs = args;
163-
final RuntimeSupport rs = RuntimeSupport.getRuntimeSupport();
162+
164163
try {
165-
try {
166-
if (AllocationSite.Options.AllocationProfiling.getValue()) {
167-
Runtime.getRuntime().addShutdownHook(new AllocationSite.AllocationProfilingShutdownHook());
168-
}
169-
if (SubstrateOptions.PrintGCSummary.getValue()) {
170-
Runtime.getRuntime().addShutdownHook(new PrintGCSummaryShutdownHook());
171-
}
172-
rs.executeStartupHooks();
173-
ImageSingletons.lookup(JavaMainSupport.class).getJavaMainMethod().invoke(null, (Object) mainArgs);
174-
} catch (Throwable ex) {
175-
JavaThreads.dispatchUncaughtException(Thread.currentThread(), ex);
164+
if (AllocationSite.Options.AllocationProfiling.getValue()) {
165+
Runtime.getRuntime().addShutdownHook(new AllocationSite.AllocationProfilingShutdownHook());
176166
}
177-
} finally {
178-
/* Shutdown before joining non-daemon threads. */
179-
rs.shutdown();
180-
}
167+
if (SubstrateOptions.PrintGCSummary.getValue()) {
168+
Runtime.getRuntime().addShutdownHook(new PrintGCSummaryShutdownHook());
169+
}
170+
RuntimeSupport.getRuntimeSupport().executeStartupHooks();
171+
172+
/*
173+
* Invoke the application's main method. Invoking the main method via a method handle
174+
* preserves exceptions, while invoking the main method via reflection would wrap
175+
* exceptions in a InvocationTargetException.
176+
*/
177+
ImageSingletons.lookup(JavaMainSupport.class).javaMainHandle.invokeExact(args);
181178

182-
JavaThreads.singleton().joinAllNonDaemons();
183-
Counter.logValues();
179+
} catch (Throwable ex) {
180+
JavaThreads.dispatchUncaughtException(Thread.currentThread(), ex);
184181

182+
} finally {
183+
/*
184+
* Shutdown sequence: First wait for all non-daemon threads to exit.
185+
*/
186+
JavaThreads.singleton().joinAllNonDaemons();
187+
/*
188+
* Run shutdown hooks (both our own hooks and application-registered hooks. Note that
189+
* this can start new non-daemon threads. We are not responsible to wait until they have
190+
* exited.
191+
*/
192+
RuntimeSupport.getRuntimeSupport().shutdown();
193+
194+
Counter.logValues();
195+
}
185196
return 0;
186197
}
187198

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java

+31-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.util.concurrent.ConcurrentHashMap;
4949
import java.util.concurrent.ConcurrentMap;
5050
import java.util.concurrent.locks.ReentrantLock;
51+
import java.util.function.Predicate;
5152

5253
import org.graalvm.compiler.api.replacements.Fold;
5354
import org.graalvm.compiler.core.common.SuppressFBWarnings;
@@ -65,6 +66,7 @@
6566
import org.graalvm.word.WordFactory;
6667

6768
import com.oracle.svm.core.MonitorSupport;
69+
import com.oracle.svm.core.SubstrateOptions;
6870
import com.oracle.svm.core.UnsafeAccess;
6971
import com.oracle.svm.core.annotate.Alias;
7072
import com.oracle.svm.core.annotate.AutomaticFeature;
@@ -545,6 +547,20 @@ static Object command(Object arg) {
545547
}
546548
}
547549

550+
final class IsSingleThreaded implements Predicate<Class<?>> {
551+
@Override
552+
public boolean test(Class<?> t) {
553+
return !SubstrateOptions.MultiThreaded.getValue();
554+
}
555+
}
556+
557+
final class IsMultiThreaded implements Predicate<Class<?>> {
558+
@Override
559+
public boolean test(Class<?> t) {
560+
return SubstrateOptions.MultiThreaded.getValue();
561+
}
562+
}
563+
548564
@TargetClass(className = "java.lang.ApplicationShutdownHooks")
549565
final class Target_java_lang_ApplicationShutdownHooks {
550566

@@ -559,9 +575,14 @@ final class Target_java_lang_ApplicationShutdownHooks {
559575
/**
560576
* Instead of starting all the threads in {@link #hooks}, just run the {@link Runnable}s one
561577
* after another.
578+
*
579+
* We need this substitution in single-threaded mode, where we cannot start new threads but
580+
* still want to support shutdown hooks. In multi-threaded mode, this substitution is not
581+
* present, i.e., the original JDK code runs the shutdown hooks in separate threads.
562582
*/
563583
@Substitute
564-
static void runHooks() {
584+
@TargetElement(name = "runHooks", onlyWith = IsSingleThreaded.class)
585+
static void runHooksSingleThreaded() {
565586
/* Claim all the hooks. */
566587
final Collection<Thread> threads;
567588
/* Checkstyle: allow synchronization. */
@@ -588,6 +609,10 @@ static void runHooks() {
588609
}
589610
}
590611

612+
@Alias
613+
@TargetElement(name = "runHooks", onlyWith = IsMultiThreaded.class)
614+
static native void runHooksMultiThreaded();
615+
591616
/**
592617
* Interpose so that the first time someone adds an ApplicationShutdownHook, I set up a shutdown
593618
* hook to run all the ApplicationShutdownHooks. Then the rest of this method is copied from
@@ -636,7 +661,11 @@ public static void initializeOnce() {
636661
new Runnable() {
637662
@Override
638663
public void run() {
639-
Target_java_lang_ApplicationShutdownHooks.runHooks();
664+
if (SubstrateOptions.MultiThreaded.getValue()) {
665+
Target_java_lang_ApplicationShutdownHooks.runHooksMultiThreaded();
666+
} else {
667+
Target_java_lang_ApplicationShutdownHooks.runHooksSingleThreaded();
668+
}
640669
}
641670
});
642671
} catch (InternalError ie) {

0 commit comments

Comments
 (0)