Check startup/shutdown thread state for close bypass in shutdown hook · spring-projects/spring-framework@a612518 (original) (raw)
`@@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
`
205
205
`/** Flag that indicates whether this context has been closed already. */
`
206
206
`private final AtomicBoolean closed = new AtomicBoolean();
`
207
207
``
208
``
`-
/** Synchronization lock for the "refresh" and "destroy". */
`
``
208
`+
/** Synchronization lock for "refresh" and "close". */
`
209
209
`private final Lock startupShutdownLock = new ReentrantLock();
`
210
210
``
``
211
`+
/** Currently active startup/shutdown thread. */
`
``
212
`+
@Nullable
`
``
213
`+
private volatile Thread startupShutdownThread;
`
``
214
+
211
215
`/** Reference to the JVM shutdown hook, if registered. */
`
212
216
`@Nullable
`
213
217
`private Thread shutdownHook;
`
`@@ -580,6 +584,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
`
580
584
`public void refresh() throws BeansException, IllegalStateException {
`
581
585
`this.startupShutdownLock.lock();
`
582
586
`try {
`
``
587
`+
this.startupShutdownThread = Thread.currentThread();
`
``
588
+
583
589
`StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
`
584
590
``
585
591
`// Prepare this context for refreshing.
`
`@@ -643,6 +649,7 @@ public void refresh() throws BeansException, IllegalStateException {
`
643
649
` }
`
644
650
` }
`
645
651
`finally {
`
``
652
`+
this.startupShutdownThread = null;
`
646
653
`this.startupShutdownLock.unlock();
`
647
654
` }
`
648
655
` }
`
`@@ -1022,20 +1029,47 @@ public void registerShutdownHook() {
`
1022
1029
`this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
`
1023
1030
`@Override
`
1024
1031
`public void run() {
`
1025
``
`-
if (startupShutdownLock.tryLock()) {
`
1026
``
`-
try {
`
1027
``
`-
doClose();
`
1028
``
`-
}
`
1029
``
`-
finally {
`
1030
``
`-
startupShutdownLock.unlock();
`
1031
``
`-
}
`
``
1032
`+
if (isStartupShutdownThreadStuck()) {
`
``
1033
`+
active.set(false);
`
``
1034
`+
return;
`
``
1035
`+
}
`
``
1036
`+
startupShutdownLock.lock();
`
``
1037
`+
try {
`
``
1038
`+
doClose();
`
``
1039
`+
}
`
``
1040
`+
finally {
`
``
1041
`+
startupShutdownLock.unlock();
`
1032
1042
` }
`
1033
1043
` }
`
1034
1044
` };
`
1035
1045
`Runtime.getRuntime().addShutdownHook(this.shutdownHook);
`
1036
1046
` }
`
1037
1047
` }
`
1038
1048
``
``
1049
`+
/**
`
``
1050
`+
- Determine whether an active startup/shutdown thread is currently stuck,
`
``
1051
`+
- e.g. through a {@code System.exit} call in a user component.
`
``
1052
`+
*/
`
``
1053
`+
private boolean isStartupShutdownThreadStuck() {
`
``
1054
`+
Thread activeThread = this.startupShutdownThread;
`
``
1055
`+
if (activeThread != null && activeThread.getState() == Thread.State.WAITING) {
`
``
1056
`+
// Indefinitely waiting: might be Thread.join or the like, or System.exit
`
``
1057
`+
activeThread.interrupt();
`
``
1058
`+
try {
`
``
1059
`+
// Leave just a little bit of time for the interruption to show effect
`
``
1060
`+
Thread.sleep(1);
`
``
1061
`+
}
`
``
1062
`+
catch (InterruptedException ex) {
`
``
1063
`+
Thread.currentThread().interrupt();
`
``
1064
`+
}
`
``
1065
`+
if (activeThread.getState() == Thread.State.WAITING) {
`
``
1066
`+
// Interrupted but still waiting: very likely a System.exit call
`
``
1067
`+
return true;
`
``
1068
`+
}
`
``
1069
`+
}
`
``
1070
`+
return false;
`
``
1071
`+
}
`
``
1072
+
1039
1073
`/**
`
1040
1074
` * Close this application context, destroying all beans in its bean factory.
`
1041
1075
` *
Delegates to {@code doClose()} for the actual closing procedure.
`
`@@ -1045,23 +1079,31 @@ public void run() {
`
1045
1079
` */
`
1046
1080
`@Override
`
1047
1081
`public void close() {
`
1048
``
`-
if (this.startupShutdownLock.tryLock()) {
`
1049
``
`-
try {
`
1050
``
`-
doClose();
`
1051
``
`-
// If we registered a JVM shutdown hook, we don't need it anymore now:
`
1052
``
`-
// We've already explicitly closed the context.
`
1053
``
`-
if (this.shutdownHook != null) {
`
1054
``
`-
try {
`
1055
``
`-
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
`
1056
``
`-
}
`
1057
``
`-
catch (IllegalStateException ex) {
`
1058
``
`-
// ignore - VM is already shutting down
`
1059
``
`-
}
`
``
1082
`+
if (isStartupShutdownThreadStuck()) {
`
``
1083
`+
this.active.set(false);
`
``
1084
`+
return;
`
``
1085
`+
}
`
``
1086
+
``
1087
`+
this.startupShutdownLock.lock();
`
``
1088
`+
try {
`
``
1089
`+
this.startupShutdownThread = Thread.currentThread();
`
``
1090
+
``
1091
`+
doClose();
`
``
1092
+
``
1093
`+
// If we registered a JVM shutdown hook, we don't need it anymore now:
`
``
1094
`+
// We've already explicitly closed the context.
`
``
1095
`+
if (this.shutdownHook != null) {
`
``
1096
`+
try {
`
``
1097
`+
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
`
``
1098
`+
}
`
``
1099
`+
catch (IllegalStateException ex) {
`
``
1100
`+
// ignore - VM is already shutting down
`
1060
1101
` }
`
1061
1102
` }
`
1062
``
`-
finally {
`
1063
``
`-
this.startupShutdownLock.unlock();
`
1064
``
`-
}
`
``
1103
`+
}
`
``
1104
`+
finally {
`
``
1105
`+
this.startupShutdownThread = null;
`
``
1106
`+
this.startupShutdownLock.unlock();
`
1065
1107
` }
`
1066
1108
` }
`
1067
1109
``