Skip to content

Commit

Permalink
Use ReentrantLock to skip intermediate close attempt from shutdown hook
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoeller committed Dec 12, 2023
1 parent f0abdf2 commit eae5356
Showing 1 changed file with 35 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.Map;
import java.util.Set;
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.LogFactory;
Expand Down Expand Up @@ -203,8 +205,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** Flag that indicates whether this context has been closed already. */
private final AtomicBoolean closed = new AtomicBoolean();

/** Synchronization monitor for the "refresh" and "destroy". */
private final Object startupShutdownMonitor = new Object();
/** Synchronization lock for the "refresh" and "destroy". */
private final Lock startupShutdownLock = new ReentrantLock();

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

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
this.startupShutdownLock.lock();
try {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

// Prepare this context for refreshing.
Expand Down Expand Up @@ -624,6 +627,7 @@ public void refresh() throws BeansException, IllegalStateException {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

Expand All @@ -638,19 +642,18 @@ public void refresh() throws BeansException, IllegalStateException {
contextRefresh.end();
}
}
finally {
this.startupShutdownLock.unlock();
}
}

/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();

// Remove shutdown hook during refresh phase.
removeShutdownHook();

// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);

Expand Down Expand Up @@ -970,9 +973,6 @@ protected void finishRefresh() {

// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));

// Restore shutdown hook if registered before.
restoreShutdownHook();
}

/**
Expand Down Expand Up @@ -1022,37 +1022,20 @@ public void registerShutdownHook() {
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
if (startupShutdownLock.tryLock()) {
try {
doClose();
}
finally {
startupShutdownLock.unlock();
}
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}

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.
* <p>Delegates to {@code doClose()} for the actual closing procedure.
Expand All @@ -1062,12 +1045,23 @@ private void restoreShutdownHook() {
*/
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We're already explicitly closing the context.
removeShutdownHook();

doClose();
if (this.startupShutdownLock.tryLock()) {
try {
doClose();
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
finally {
this.startupShutdownLock.unlock();
}
}
}

Expand Down

0 comments on commit eae5356

Please sign in to comment.