Skip to content

Commit e53d316

Browse files
committed
Set TCCL of shutdown thread when triggered by the shutdown endpoint
Previously, the shutdown endpoint would spawn a new thread to perform the shutdown but did not explicitly configure its thread context class loader (TCCL). This mean that the new thread would use the request thread's TCCL as its TCCL. This meant that a different TCCL would be used compared to a shutdown triggered by the shutdown hook and also caused problems with Tomcat's thread leak detection logic. This commit updates the shutdown endpoint to explicitly configure the TCCL of the shutdown thread to be the ClassLoader that loaded the endpoint's class. Closes gh-6361
1 parent 78879f4 commit e53d316

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
*
3131
* @author Dave Syer
3232
* @author Christian Dupuis
33+
* @author Andy Wilkinson
3334
*/
3435
@ConfigurationProperties(prefix = "endpoints.shutdown")
3536
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
@@ -58,7 +59,7 @@ public Map<String, Object> invoke() {
5859
}
5960
finally {
6061

61-
new Thread(new Runnable() {
62+
Thread thread = new Thread(new Runnable() {
6263
@Override
6364
public void run() {
6465
try {
@@ -69,8 +70,9 @@ public void run() {
6970
}
7071
ShutdownEndpoint.this.context.close();
7172
}
72-
}).start();
73-
73+
});
74+
thread.setContextClassLoader(getClass().getClassLoader());
75+
thread.start();
7476
}
7577
}
7678

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.actuate.endpoint;
1818

19+
import java.net.URL;
20+
import java.net.URLClassLoader;
21+
import java.util.Map;
1922
import java.util.concurrent.CountDownLatch;
2023
import java.util.concurrent.TimeUnit;
2124

@@ -28,6 +31,7 @@
2831
import org.springframework.context.event.ContextClosedEvent;
2932

3033
import static org.hamcrest.Matchers.equalTo;
34+
import static org.hamcrest.Matchers.is;
3135
import static org.hamcrest.Matchers.startsWith;
3236
import static org.junit.Assert.assertThat;
3337
import static org.junit.Assert.assertTrue;
@@ -37,6 +41,7 @@
3741
*
3842
* @author Phillip Webb
3943
* @author Dave Syer
44+
* @author Andy Wilkinson
4045
*/
4146
public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoint> {
4247

@@ -53,18 +58,30 @@ public void isEnabledByDefault() throws Exception {
5358

5459
@Test
5560
public void invoke() throws Exception {
56-
CountDownLatch latch = this.context.getBean(Config.class).latch;
57-
assertThat((String) getEndpointBean().invoke().get("message"),
58-
startsWith("Shutting down"));
61+
Config config = this.context.getBean(Config.class);
62+
ClassLoader previousTccl = Thread.currentThread().getContextClassLoader();
63+
Map<String, Object> result;
64+
Thread.currentThread().setContextClassLoader(
65+
new URLClassLoader(new URL[0], getClass().getClassLoader()));
66+
try {
67+
result = getEndpointBean().invoke();
68+
}
69+
finally {
70+
Thread.currentThread().setContextClassLoader(previousTccl);
71+
}
72+
assertThat((String) result.get("message"), startsWith("Shutting down"));
5973
assertTrue(this.context.isActive());
60-
assertTrue(latch.await(10, TimeUnit.SECONDS));
74+
assertTrue(config.latch.await(10, TimeUnit.SECONDS));
75+
assertThat(config.threadContextClassLoader, is(getClass().getClassLoader()));
6176
}
6277

6378
@Configuration
6479
@EnableConfigurationProperties
6580
public static class Config {
6681

67-
private CountDownLatch latch = new CountDownLatch(1);
82+
private final CountDownLatch latch = new CountDownLatch(1);
83+
84+
private volatile ClassLoader threadContextClassLoader;
6885

6986
@Bean
7087
public ShutdownEndpoint endpoint() {
@@ -77,6 +94,8 @@ public ApplicationListener<ContextClosedEvent> listener() {
7794
return new ApplicationListener<ContextClosedEvent>() {
7895
@Override
7996
public void onApplicationEvent(ContextClosedEvent event) {
97+
Config.this.threadContextClassLoader = Thread.currentThread()
98+
.getContextClassLoader();
8099
Config.this.latch.countDown();
81100
}
82101
};

0 commit comments

Comments
 (0)