Skip to content

Commit

Permalink
Register application shutdown hook lazily
Browse files Browse the repository at this point in the history
Previously, SpringApplicationShutdownHook would always register a
shutdown hook, even if SpringApplication was configured not to
use a shutdown hook, such as in a war deployment. This could
result in a memory leak when the war was undeployed. The shutdown
hook registered by SpringApplicationShutdownHook would remain
registered, pinning the web application's class loader in memory.

This commit updates SpringApplicationShutdownHook so that it
registers a shutdown hook with the JVM lazily, upon registeration
of the first application context.

Fixes gh-27987
  • Loading branch information
wilkinsona committed Sep 16, 2021
1 parent afb81f1 commit a4f1d32
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 14 deletions.
Expand Up @@ -24,6 +24,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -59,33 +60,38 @@ class SpringApplicationShutdownHook implements Runnable {

private final ApplicationContextClosedListener contextCloseListener = new ApplicationContextClosedListener();

private boolean inProgress;
private final AtomicBoolean shutdownHookAdded = new AtomicBoolean(false);

SpringApplicationShutdownHook() {
try {
addRuntimeShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments
}
}

protected void addRuntimeShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
}
private boolean inProgress;

SpringApplicationShutdownHandlers getHandlers() {
return this.handlers;
}

void registerApplicationContext(ConfigurableApplicationContext context) {
addRuntimeShutdownHookIfNecessary();
synchronized (SpringApplicationShutdownHook.class) {
assertNotInProgress();
context.addApplicationListener(this.contextCloseListener);
this.contexts.add(context);
}
}

private void addRuntimeShutdownHookIfNecessary() {
if (this.shutdownHookAdded.compareAndSet(false, true)) {
addRuntimeShutdownHook();
}
}

void addRuntimeShutdownHook() {
try {
Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
}
catch (AccessControlException ex) {
// Not allowed in some environments
}
}

@Override
public void run() {
Set<ConfigurableApplicationContext> contexts;
Expand Down
Expand Up @@ -46,8 +46,11 @@
class SpringApplicationShutdownHookTests {

@Test
void createCallsRegister() {
void shutdownHookIsNotAddedUntilContextIsRegistered() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
ConfigurableApplicationContext context = new GenericApplicationContext();
shutdownHook.registerApplicationContext(context);
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
}

Expand Down

0 comments on commit a4f1d32

Please sign in to comment.