Skip to content

Commit eae5356

Browse files
committed
Use ReentrantLock to skip intermediate close attempt from shutdown hook
See gh-31811
1 parent f0abdf2 commit eae5356

File tree

1 file changed

+35
-41
lines changed

1 file changed

+35
-41
lines changed

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

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929
import java.util.concurrent.atomic.AtomicBoolean;
30+
import java.util.concurrent.locks.Lock;
31+
import java.util.concurrent.locks.ReentrantLock;
3032

3133
import org.apache.commons.logging.Log;
3234
import org.apache.commons.logging.LogFactory;
@@ -203,8 +205,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
203205
/** Flag that indicates whether this context has been closed already. */
204206
private final AtomicBoolean closed = new AtomicBoolean();
205207

206-
/** Synchronization monitor for the "refresh" and "destroy". */
207-
private final Object startupShutdownMonitor = new Object();
208+
/** Synchronization lock for the "refresh" and "destroy". */
209+
private final Lock startupShutdownLock = new ReentrantLock();
208210

209211
/** Reference to the JVM shutdown hook, if registered. */
210212
@Nullable
@@ -576,7 +578,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
576578

577579
@Override
578580
public void refresh() throws BeansException, IllegalStateException {
579-
synchronized (this.startupShutdownMonitor) {
581+
this.startupShutdownLock.lock();
582+
try {
580583
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
581584

582585
// Prepare this context for refreshing.
@@ -624,6 +627,7 @@ public void refresh() throws BeansException, IllegalStateException {
624627
logger.warn("Exception encountered during context initialization - " +
625628
"cancelling refresh attempt: " + ex);
626629
}
630+
627631
// Destroy already created singletons to avoid dangling resources.
628632
destroyBeans();
629633

@@ -638,19 +642,18 @@ public void refresh() throws BeansException, IllegalStateException {
638642
contextRefresh.end();
639643
}
640644
}
645+
finally {
646+
this.startupShutdownLock.unlock();
647+
}
641648
}
642649

643650
/**
644651
* Prepare this context for refreshing, setting its startup date and
645652
* active flag as well as performing any initialization of property sources.
646653
*/
647654
protected void prepareRefresh() {
648-
this.startupDate = System.currentTimeMillis();
649-
650-
// Remove shutdown hook during refresh phase.
651-
removeShutdownHook();
652-
653655
// Switch to active.
656+
this.startupDate = System.currentTimeMillis();
654657
this.closed.set(false);
655658
this.active.set(true);
656659

@@ -970,9 +973,6 @@ protected void finishRefresh() {
970973

971974
// Publish the final event.
972975
publishEvent(new ContextRefreshedEvent(this));
973-
974-
// Restore shutdown hook if registered before.
975-
restoreShutdownHook();
976976
}
977977

978978
/**
@@ -1022,37 +1022,20 @@ public void registerShutdownHook() {
10221022
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
10231023
@Override
10241024
public void run() {
1025-
synchronized (startupShutdownMonitor) {
1026-
doClose();
1025+
if (startupShutdownLock.tryLock()) {
1026+
try {
1027+
doClose();
1028+
}
1029+
finally {
1030+
startupShutdownLock.unlock();
1031+
}
10271032
}
10281033
}
10291034
};
10301035
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
10311036
}
10321037
}
10331038

1034-
private void removeShutdownHook() {
1035-
if (this.shutdownHook != null) {
1036-
try {
1037-
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
1038-
}
1039-
catch (IllegalStateException ex) {
1040-
// ignore - VM is already shutting down
1041-
}
1042-
}
1043-
}
1044-
1045-
private void restoreShutdownHook() {
1046-
if (this.shutdownHook != null) {
1047-
try {
1048-
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
1049-
}
1050-
catch (IllegalStateException | IllegalArgumentException ex) {
1051-
// ignore - VM is already shutting down or hook already registered
1052-
}
1053-
}
1054-
}
1055-
10561039
/**
10571040
* Close this application context, destroying all beans in its bean factory.
10581041
* <p>Delegates to {@code doClose()} for the actual closing procedure.
@@ -1062,12 +1045,23 @@ private void restoreShutdownHook() {
10621045
*/
10631046
@Override
10641047
public void close() {
1065-
synchronized (this.startupShutdownMonitor) {
1066-
// If we registered a JVM shutdown hook, we don't need it anymore now:
1067-
// We're already explicitly closing the context.
1068-
removeShutdownHook();
1069-
1070-
doClose();
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+
}
1060+
}
1061+
}
1062+
finally {
1063+
this.startupShutdownLock.unlock();
1064+
}
10711065
}
10721066
}
10731067

0 commit comments

Comments
 (0)