Skip to content

Commit a612518

Browse files
committed
Check startup/shutdown thread state for close bypass in shutdown hook
See gh-31811
1 parent ec0ec7a commit a612518

File tree

1 file changed

+65
-23
lines changed

1 file changed

+65
-23
lines changed

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
205205
/** Flag that indicates whether this context has been closed already. */
206206
private final AtomicBoolean closed = new AtomicBoolean();
207207

208-
/** Synchronization lock for the "refresh" and "destroy". */
208+
/** Synchronization lock for "refresh" and "close". */
209209
private final Lock startupShutdownLock = new ReentrantLock();
210210

211+
/** Currently active startup/shutdown thread. */
212+
@Nullable
213+
private volatile Thread startupShutdownThread;
214+
211215
/** Reference to the JVM shutdown hook, if registered. */
212216
@Nullable
213217
private Thread shutdownHook;
@@ -580,6 +584,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
580584
public void refresh() throws BeansException, IllegalStateException {
581585
this.startupShutdownLock.lock();
582586
try {
587+
this.startupShutdownThread = Thread.currentThread();
588+
583589
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
584590

585591
// Prepare this context for refreshing.
@@ -643,6 +649,7 @@ public void refresh() throws BeansException, IllegalStateException {
643649
}
644650
}
645651
finally {
652+
this.startupShutdownThread = null;
646653
this.startupShutdownLock.unlock();
647654
}
648655
}
@@ -1022,20 +1029,47 @@ public void registerShutdownHook() {
10221029
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
10231030
@Override
10241031
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();
10321042
}
10331043
}
10341044
};
10351045
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
10361046
}
10371047
}
10381048

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+
10391073
/**
10401074
* Close this application context, destroying all beans in its bean factory.
10411075
* <p>Delegates to {@code doClose()} for the actual closing procedure.
@@ -1045,23 +1079,31 @@ public void run() {
10451079
*/
10461080
@Override
10471081
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
10601101
}
10611102
}
1062-
finally {
1063-
this.startupShutdownLock.unlock();
1064-
}
1103+
}
1104+
finally {
1105+
this.startupShutdownThread = null;
1106+
this.startupShutdownLock.unlock();
10651107
}
10661108
}
10671109

0 commit comments

Comments
 (0)