Use ReentrantLock to skip intermediate close attempt from shutdown hook

See gh-31811
This commit is contained in:
Juergen Hoeller 2023-12-12 12:39:45 +01:00
parent f0abdf2264
commit eae53560e4
1 changed files with 35 additions and 41 deletions

View File

@ -27,6 +27,8 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -203,8 +205,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** Flag that indicates whether this context has been closed already. */ /** Flag that indicates whether this context has been closed already. */
private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean closed = new AtomicBoolean();
/** Synchronization monitor for the "refresh" and "destroy". */ /** Synchronization lock for the "refresh" and "destroy". */
private final Object startupShutdownMonitor = new Object(); private final Lock startupShutdownLock = new ReentrantLock();
/** Reference to the JVM shutdown hook, if registered. */ /** Reference to the JVM shutdown hook, if registered. */
@Nullable @Nullable
@ -576,7 +578,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
@Override @Override
public void refresh() throws BeansException, IllegalStateException { public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) { this.startupShutdownLock.lock();
try {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing. // Prepare this context for refreshing.
@ -624,6 +627,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
logger.warn("Exception encountered during context initialization - " + logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex); "cancelling refresh attempt: " + ex);
} }
// Destroy already created singletons to avoid dangling resources. // Destroy already created singletons to avoid dangling resources.
destroyBeans(); destroyBeans();
@ -638,6 +642,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
contextRefresh.end(); contextRefresh.end();
} }
} }
finally {
this.startupShutdownLock.unlock();
}
} }
/** /**
@ -645,12 +652,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* active flag as well as performing any initialization of property sources. * active flag as well as performing any initialization of property sources.
*/ */
protected void prepareRefresh() { protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
// Remove shutdown hook during refresh phase.
removeShutdownHook();
// Switch to active. // Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false); this.closed.set(false);
this.active.set(true); this.active.set(true);
@ -970,9 +973,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Publish the final event. // Publish the final event.
publishEvent(new ContextRefreshedEvent(this)); publishEvent(new ContextRefreshedEvent(this));
// Restore shutdown hook if registered before.
restoreShutdownHook();
} }
/** /**
@ -1022,8 +1022,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) { this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override @Override
public void run() { public void run() {
synchronized (startupShutdownMonitor) { if (startupShutdownLock.tryLock()) {
doClose(); try {
doClose();
}
finally {
startupShutdownLock.unlock();
}
} }
} }
}; };
@ -1031,28 +1036,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
} }
} }
private void removeShutdownHook() {
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
private void restoreShutdownHook() {
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
catch (IllegalStateException | IllegalArgumentException ex) {
// ignore - VM is already shutting down or hook already registered
}
}
}
/** /**
* Close this application context, destroying all beans in its bean factory. * Close this application context, destroying all beans in its bean factory.
* <p>Delegates to {@code doClose()} for the actual closing procedure. * <p>Delegates to {@code doClose()} for the actual closing procedure.
@ -1062,12 +1045,23 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/ */
@Override @Override
public void close() { public void close() {
synchronized (this.startupShutdownMonitor) { if (this.startupShutdownLock.tryLock()) {
// If we registered a JVM shutdown hook, we don't need it anymore now: try {
// We're already explicitly closing the context. doClose();
removeShutdownHook(); // If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
doClose(); if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
finally {
this.startupShutdownLock.unlock();
}
} }
} }