Skip to content

Commit 9371802

Browse files
committed
Merge pull request #334 from JLLeitschuh/fix/windowsShutdownHandling
Fix windows hanging when being shutdown
2 parents ca18472 + 694d9e4 commit 9371802

File tree

3 files changed

+111
-21
lines changed

3 files changed

+111
-21
lines changed

core/src/main/java/edu/wpi/grip/core/events/UnexpectedThrowableEvent.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package edu.wpi.grip.core.events;
22

3+
import edu.wpi.grip.core.util.SafeShutdown;
4+
35
import java.util.logging.Level;
46
import java.util.logging.Logger;
57

@@ -50,7 +52,7 @@ public void handleSafely(UnexpectedThrowableEventHandler handler) {
5052
try {
5153
logger.log(Level.SEVERE, "Failed to handle safely", throwable);
5254
} finally {
53-
System.exit(1);
55+
SafeShutdown.exit(1);
5456
}
5557
} finally {
5658
shutdownIfFatal();
@@ -67,7 +69,7 @@ public void shutdownIfFatal() {
6769
logger.log(Level.SEVERE, "Shutting down from error", throwable);
6870
} finally {
6971
// If all else fails then shutdown
70-
System.exit(1);
72+
SafeShutdown.exit(1);
7173
}
7274
}
7375
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package edu.wpi.grip.core.util;
2+
3+
/**
4+
* This class should be used to shutdown GRIP safely.
5+
* This is because shutdown hooks may throw exceptions, as such
6+
* we need to know if the application being is shutdown.
7+
*/
8+
public final class SafeShutdown {
9+
10+
public static volatile boolean stopping = false;
11+
12+
static {
13+
/*
14+
* Shutdown hook run order is non-deterministic but this increases our likelihood of flagging a shutdown
15+
* that we can't control.
16+
*/
17+
Runtime.getRuntime().addShutdownHook(new Thread() {
18+
@Override
19+
public void run() {
20+
stopping = true;
21+
}
22+
});
23+
}
24+
25+
/**
26+
* Shutdown's the VM in such a way that flags that the vm is stopping.
27+
* This is so that we don't run the normal exception handling code when
28+
* shutting down the application.
29+
*
30+
* @param statusCode exit status.
31+
* @param hook The hook to run before the System shutdown.
32+
* This will be run after stopping has been flagged true.
33+
* This is nullable.
34+
* @see System#exit(int)
35+
*/
36+
public static final void exit(int statusCode, PreSystemExitHook hook) {
37+
flagStopping();
38+
try {
39+
if (hook != null) {
40+
hook.run();
41+
}
42+
} finally {
43+
System.exit(statusCode);
44+
}
45+
46+
}
47+
48+
/**
49+
* Helper method that passes null as the PreSystemExitHook.
50+
*
51+
* @param statusCode exit status.
52+
* @see #exit(int)
53+
*/
54+
public static final void exit(int statusCode) {
55+
exit(statusCode, null);
56+
}
57+
58+
59+
/**
60+
* HACK!
61+
* Shutdown hooks can throw exceptions.
62+
* On Windows, the static method after {@link org.bytedeco.javacpp.Loader#loadLibrary} throws such an exception
63+
* in a shutdown hook.
64+
*
65+
* @return True if if the application is shutting down.
66+
* @see <a href="https://github.com/WPIRoboticsProjects/GRIP/issues/297">GRIP Issue</a>
67+
* @see <a href="https://github.com/bytedeco/javacpp/issues/60">Bytedeco issue</a>
68+
*/
69+
public static final boolean isStopping() {
70+
return stopping;
71+
}
72+
73+
/**
74+
* Flags that the application is shutting down.
75+
*/
76+
public static void flagStopping() {
77+
stopping = true;
78+
}
79+
80+
@FunctionalInterface
81+
public interface PreSystemExitHook {
82+
void run();
83+
}
84+
}

ui/src/main/java/edu/wpi/grip/ui/Main.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import edu.wpi.grip.core.Palette;
1010
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
1111
import edu.wpi.grip.core.operations.Operations;
12+
import edu.wpi.grip.core.util.SafeShutdown;
1213
import edu.wpi.grip.generated.CVOperations;
1314
import edu.wpi.grip.ui.util.DPIUtility;
1415
import javafx.application.Application;
@@ -32,9 +33,6 @@ public class Main extends Application {
3233
@Inject
3334
private Logger logger;
3435

35-
private volatile boolean stopping = false;
36-
37-
3836
protected final Injector injector = Guice.createInjector(new GRIPCoreModule(), new GRIPUIModule());
3937

4038
private final Object dialogLock = new Object();
@@ -62,8 +60,9 @@ public void start(Stage stage) throws Exception {
6260
CVOperations.addOperations(eventBus);
6361

6462
stage.setOnCloseRequest((event) -> {
65-
Platform.exit();
66-
System.exit(0);
63+
// If this isn't here this can cause a deadlock on windows
64+
// See issue #297
65+
SafeShutdown.exit(0, Platform::exit);
6766
});
6867

6968
stage.setTitle("GRIP Computer Vision Engine");
@@ -74,30 +73,35 @@ public void start(Stage stage) throws Exception {
7473
}
7574

7675
public void stop() {
77-
stopping = true;
76+
SafeShutdown.flagStopping();
7877
}
7978

8079
@Subscribe
8180
@SuppressWarnings("PMD.AvoidCatchingThrowable")
8281
public final void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
8382
event.handleSafely((throwable, message, isFatal) -> {
84-
// This should still use PlatformImpl
85-
if (!stopping) {
83+
// Check this so we can avoid entering the the platform wait
84+
// if the program is shutting down.
85+
if (!SafeShutdown.isStopping()) {
86+
// This should still use PlatformImpl
8687
PlatformImpl.runAndWait(() -> {
8788
// WARNING! Do not post any events from within this! It could result in a deadlock!
8889
synchronized (this.dialogLock) {
89-
try {
90-
// Don't create more than one exception dialog at the same time
91-
final ExceptionAlert exceptionAlert = new ExceptionAlert(root, throwable, message, isFatal, getHostServices());
92-
exceptionAlert.setInitialFocus();
93-
exceptionAlert.showAndWait();
94-
} catch (Throwable e) {
95-
// Well in this case something has gone very, very wrong
96-
// We don't want to create a feedback loop either.
90+
// Check again because the value could have been changed while waiting for the javafx thread to run.
91+
if (!SafeShutdown.isStopping()) {
9792
try {
98-
logger.log(Level.SEVERE, "Failed to show exception alert", e);
99-
} finally {
100-
System.exit(1); // Ensure we shut down the application if we get an exception
93+
// Don't create more than one exception dialog at the same time
94+
final ExceptionAlert exceptionAlert = new ExceptionAlert(root, throwable, message, isFatal, getHostServices());
95+
exceptionAlert.setInitialFocus();
96+
exceptionAlert.showAndWait();
97+
} catch (Throwable e) {
98+
// Well in this case something has gone very, very wrong
99+
// We don't want to create a feedback loop either.
100+
try {
101+
logger.log(Level.SEVERE, "Failed to show exception alert", e);
102+
} finally {
103+
SafeShutdown.exit(1); // Ensure we shut down the application if we get an exception
104+
}
101105
}
102106
}
103107
}

0 commit comments

Comments
 (0)