Skip to content

Commit c187bd9

Browse files
committed
Don't add runtime shutdown hook till app with hook enabled is run
Previously, the runtime shutdown hook was added as soon as a shutdown handler was registered. This causes a memory leak in a war deployment when the application is undeployed as LoggingApplicationListener always registers a shutdown handler so the runtime shutdown hook was always registered. This commit updates the shutdown hook so that the runtime shutdown hook is only allowed to be added once run() has been called on a SpringApplication with the shutdown hook enabled. This approach allows the registerShutdownHook flag on SpringApplication to be a central point of control for the registration of the runtime shutdown hook. When that flag is set to false, for example by SpringBootServletInitializer, the runtime shutdown hook will not be registered, irrespective of whether other code uses the public API to add a shutdown handler. An alternative approach of stopping LoggingApplicationListener from adding its shutdown handler – for example by adding logging.register-shutdown-hook=false to the environment – was considered. This approach was rejected in favor of the centralized approach described above as it would require every caller that adds a shutdown handler to deal with the problem. Closes gh-37096
1 parent d9207fc commit c187bd9

File tree

3 files changed

+24
-1
lines changed

3 files changed

+24
-1
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ private Optional<Class<?>> findMainClass(Stream<StackFrame> stack) {
296296
* @return a running {@link ApplicationContext}
297297
*/
298298
public ConfigurableApplicationContext run(String... args) {
299+
if (this.registerShutdownHook) {
300+
SpringApplication.shutdownHook.enableShutdowHookAddition();
301+
}
299302
long startTime = System.nanoTime();
300303
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
301304
ConfigurableApplicationContext context = null;

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,18 @@ class SpringApplicationShutdownHook implements Runnable {
6262

6363
private final AtomicBoolean shutdownHookAdded = new AtomicBoolean();
6464

65+
private volatile boolean shutdownHookAdditionEnabled = false;
66+
6567
private boolean inProgress;
6668

6769
SpringApplicationShutdownHandlers getHandlers() {
6870
return this.handlers;
6971
}
7072

73+
void enableShutdowHookAddition() {
74+
this.shutdownHookAdditionEnabled = true;
75+
}
76+
7177
void registerApplicationContext(ConfigurableApplicationContext context) {
7278
addRuntimeShutdownHookIfNecessary();
7379
synchronized (SpringApplicationShutdownHook.class) {
@@ -78,7 +84,7 @@ void registerApplicationContext(ConfigurableApplicationContext context) {
7884
}
7985

8086
private void addRuntimeShutdownHookIfNecessary() {
81-
if (this.shutdownHookAdded.compareAndSet(false, true)) {
87+
if (this.shutdownHookAdditionEnabled && this.shutdownHookAdded.compareAndSet(false, true)) {
8288
addRuntimeShutdownHook();
8389
}
8490
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class SpringApplicationShutdownHookTests {
5151
@Test
5252
void shutdownHookIsNotAddedUntilContextIsRegistered() {
5353
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
54+
shutdownHook.enableShutdowHookAddition();
5455
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
5556
ConfigurableApplicationContext context = new GenericApplicationContext();
5657
shutdownHook.registerApplicationContext(context);
@@ -60,12 +61,25 @@ void shutdownHookIsNotAddedUntilContextIsRegistered() {
6061
@Test
6162
void shutdownHookIsNotAddedUntilHandlerIsRegistered() {
6263
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
64+
shutdownHook.enableShutdowHookAddition();
6365
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
6466
shutdownHook.getHandlers().add(() -> {
6567
});
6668
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
6769
}
6870

71+
@Test
72+
void shutdownHookIsNotAddedUntilAdditionIsEnabled() {
73+
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
74+
shutdownHook.getHandlers().add(() -> {
75+
});
76+
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
77+
shutdownHook.enableShutdowHookAddition();
78+
shutdownHook.getHandlers().add(() -> {
79+
});
80+
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
81+
}
82+
6983
@Test
7084
void runClosesContextsBeforeRunningHandlerActions() {
7185
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();

0 commit comments

Comments
 (0)