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
This commit is contained in:
Andy Wilkinson 2016-07-11 11:16:34 +01:00
parent 78879f4bdb
commit e53d3167ab
2 changed files with 30 additions and 9 deletions

View File

@ -30,6 +30,7 @@ import org.springframework.context.ConfigurableApplicationContext;
*
* @author Dave Syer
* @author Christian Dupuis
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
@ -58,7 +59,7 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
}
finally {
new Thread(new Runnable() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
@ -69,8 +70,9 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
}
ShutdownEndpoint.this.context.close();
}
}).start();
});
thread.setContextClassLoader(getClass().getClassLoader());
thread.start();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,9 @@
package org.springframework.boot.actuate.endpoint;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -28,6 +31,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -37,6 +41,7 @@ import static org.junit.Assert.assertTrue;
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
*/
public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoint> {
@ -53,18 +58,30 @@ public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoin
@Test
public void invoke() throws Exception {
CountDownLatch latch = this.context.getBean(Config.class).latch;
assertThat((String) getEndpointBean().invoke().get("message"),
startsWith("Shutting down"));
Config config = this.context.getBean(Config.class);
ClassLoader previousTccl = Thread.currentThread().getContextClassLoader();
Map<String, Object> result;
Thread.currentThread().setContextClassLoader(
new URLClassLoader(new URL[0], getClass().getClassLoader()));
try {
result = getEndpointBean().invoke();
}
finally {
Thread.currentThread().setContextClassLoader(previousTccl);
}
assertThat((String) result.get("message"), startsWith("Shutting down"));
assertTrue(this.context.isActive());
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertTrue(config.latch.await(10, TimeUnit.SECONDS));
assertThat(config.threadContextClassLoader, is(getClass().getClassLoader()));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
private CountDownLatch latch = new CountDownLatch(1);
private final CountDownLatch latch = new CountDownLatch(1);
private volatile ClassLoader threadContextClassLoader;
@Bean
public ShutdownEndpoint endpoint() {
@ -77,6 +94,8 @@ public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoin
return new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
Config.this.threadContextClassLoader = Thread.currentThread()
.getContextClassLoader();
Config.this.latch.countDown();
}
};