Browse Source

Check startup/shutdown thread state for close bypass in shutdown hook

See gh-31811
pull/31844/head
Juergen Hoeller 2 years ago
parent
commit
a612518f96
  1. 88
      spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

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

@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** Flag that indicates whether this context has been closed already. */
private final AtomicBoolean closed = new AtomicBoolean();
/** Synchronization lock for the "refresh" and "destroy". */
/** Synchronization lock for "refresh" and "close". */
private final Lock startupShutdownLock = new ReentrantLock();
/** Currently active startup/shutdown thread. */
@Nullable
private volatile Thread startupShutdownThread;
/** Reference to the JVM shutdown hook, if registered. */
@Nullable
private Thread shutdownHook;
@ -580,6 +584,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -580,6 +584,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
public void refresh() throws BeansException, IllegalStateException {
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
@ -643,6 +649,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -643,6 +649,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}
@ -1022,13 +1029,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -1022,13 +1029,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
if (startupShutdownLock.tryLock()) {
try {
doClose();
}
finally {
startupShutdownLock.unlock();
}
if (isStartupShutdownThreadStuck()) {
active.set(false);
return;
}
startupShutdownLock.lock();
try {
doClose();
}
finally {
startupShutdownLock.unlock();
}
}
};
@ -1036,6 +1046,30 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -1036,6 +1046,30 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
/**
* Determine whether an active startup/shutdown thread is currently stuck,
* e.g. through a {@code System.exit} call in a user component.
*/
private boolean isStartupShutdownThreadStuck() {
Thread activeThread = this.startupShutdownThread;
if (activeThread != null && activeThread.getState() == Thread.State.WAITING) {
// Indefinitely waiting: might be Thread.join or the like, or System.exit
activeThread.interrupt();
try {
// Leave just a little bit of time for the interruption to show effect
Thread.sleep(1);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
if (activeThread.getState() == Thread.State.WAITING) {
// Interrupted but still waiting: very likely a System.exit call
return true;
}
}
return false;
}
/**
* Close this application context, destroying all beans in its bean factory.
* <p>Delegates to {@code doClose()} for the actual closing procedure.
@ -1045,23 +1079,31 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader @@ -1045,23 +1079,31 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
@Override
public void close() {
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
}
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return;
}
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
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();
}
}
finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}

Loading…
Cancel
Save