Skip to content

Commit

Permalink
Check startup/shutdown thread state for close bypass in shutdown hook
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoeller committed Dec 13, 2023
1 parent ec0ec7a commit a612518
Showing 1 changed file with 65 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -580,6 +584,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
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.
Expand Down Expand Up @@ -643,6 +649,7 @@ public void refresh() throws BeansException, IllegalStateException {
}
}
finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}
Expand Down Expand Up @@ -1022,20 +1029,47 @@ public void registerShutdownHook() {
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();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}

/**
* 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.
Expand All @@ -1045,23 +1079,31 @@ public void run() {
*/
@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();
}
}

Expand Down

0 comments on commit a612518

Please sign in to comment.