From 7c4e46e538f05c32df9a7b10fa7df25f389edfc0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 4 Oct 2022 16:58:08 +0100 Subject: [PATCH] Allow thread dump endpoint to call ThreadMXBean in a native image Closes gh-31680 --- .../ThreadDumpEndpointAutoConfiguration.java | 11 ---- ...eadDumpEndpointAutoConfigurationTests.java | 18 ++---- .../management/ThreadDumpEndpoint.java | 37 +---------- .../ThreadDumpEndpointWebExtension.java | 60 ------------------ .../ThreadDumpEndpointWebExtensionTests.java | 63 ------------------- 5 files changed, 6 insertions(+), 183 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java index 939e74de191..d5e00f03e58 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java @@ -17,12 +17,9 @@ package org.springframework.boot.actuate.autoconfigure.management; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; -import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.management.ThreadDumpEndpoint; -import org.springframework.boot.actuate.management.ThreadDumpEndpointWebExtension; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -42,12 +39,4 @@ public class ThreadDumpEndpointAutoConfiguration { return new ThreadDumpEndpoint(); } - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(ThreadDumpEndpoint.class) - @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) - public ThreadDumpEndpointWebExtension threadDumpWebExtension(ThreadDumpEndpoint threadDumpEndpoint) { - return new ThreadDumpEndpointWebExtension(threadDumpEndpoint); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java index 5d8d895a6b8..b87afc45b21 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java @@ -19,7 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.management; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.management.ThreadDumpEndpoint; -import org.springframework.boot.actuate.management.ThreadDumpEndpointWebExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -38,27 +37,20 @@ class ThreadDumpEndpointAutoConfigurationTests { @Test void runShouldHaveEndpointBean() { - this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=threaddump").run((context) -> { - assertThat(context).hasSingleBean(ThreadDumpEndpoint.class); - assertThat(context).hasSingleBean(ThreadDumpEndpointWebExtension.class); - }); + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=threaddump") + .run((context) -> assertThat(context).hasSingleBean(ThreadDumpEndpoint.class)); } @Test void runWhenNotExposedShouldNotHaveEndpointBean() { - this.contextRunner.run((context) -> { - assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class); - assertThat(context).doesNotHaveBean(ThreadDumpEndpointWebExtension.class); - }); + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class)); } @Test void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*") - .withPropertyValues("management.endpoint.threaddump.enabled:false").run((context) -> { - assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class); - assertThat(context).doesNotHaveBean(ThreadDumpEndpointWebExtension.class); - }); + .withPropertyValues("management.endpoint.threaddump.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpoint.java index a06841d9329..90a168c5bf2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpoint.java @@ -24,7 +24,6 @@ import java.util.function.Function; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.core.NativeDetector; /** * {@link Endpoint @Endpoint} to expose thread info. @@ -49,41 +48,7 @@ public class ThreadDumpEndpoint { } private T getFormattedThreadDump(Function formatter) { - ThreadDumper threadDumper = createThreadDumper(); - return formatter.apply(threadDumper.dumpAllThreads()); - } - - private ThreadDumper createThreadDumper() { - if (NativeDetector.inNativeImage()) { - throw new ThreadDumperUnavailableException("Running in native image"); - } - return new ThreadMXBeanThreadDumper(); - } - - private interface ThreadDumper { - - ThreadInfo[] dumpAllThreads(); - - } - - private static class ThreadMXBeanThreadDumper implements ThreadDumper { - - @Override - public ThreadInfo[] dumpAllThreads() { - return ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); - } - - } - - /** - * Exception to be thrown if the {@link ThreadDumper} cannot be created. - */ - static class ThreadDumperUnavailableException extends RuntimeException { - - ThreadDumperUnavailableException(String message) { - super(message); - } - + return formatter.apply(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)); } /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java deleted file mode 100644 index 76a1f38a301..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.management; - -import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; -import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; -import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumpDescriptor; -import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumperUnavailableException; - -/** - * {@link EndpointWebExtension @EndpointWebExtension} for the {@link ThreadDumpEndpoint}. - * - * @author Moritz Halbritter - * @since 3.0.0 - */ -@EndpointWebExtension(endpoint = ThreadDumpEndpoint.class) -public class ThreadDumpEndpointWebExtension { - - private final ThreadDumpEndpoint delegate; - - public ThreadDumpEndpointWebExtension(ThreadDumpEndpoint delegate) { - this.delegate = delegate; - } - - @ReadOperation - public WebEndpointResponse threadDump() { - try { - return new WebEndpointResponse<>(this.delegate.threadDump()); - } - catch (ThreadDumperUnavailableException ex) { - return new WebEndpointResponse<>(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); - } - } - - @ReadOperation(produces = "text/plain;charset=UTF-8") - public WebEndpointResponse textThreadDump() { - try { - return new WebEndpointResponse<>(this.delegate.textThreadDump()); - } - catch (ThreadDumperUnavailableException ex) { - return new WebEndpointResponse<>(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); - } - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java deleted file mode 100644 index a565b049191..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2012-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.management; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; -import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumpDescriptor; -import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumperUnavailableException; -import org.springframework.http.HttpStatus; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ThreadDumpEndpointWebExtension}. - * - * @author Moritz Halbritter - */ -class ThreadDumpEndpointWebExtensionTests { - - private ThreadDumpEndpointWebExtension extension; - - private ThreadDumpEndpoint delegateMock; - - @BeforeEach - void setUp() { - this.delegateMock = Mockito.mock(ThreadDumpEndpoint.class); - this.extension = new ThreadDumpEndpointWebExtension(this.delegateMock); - } - - @Test - void shouldHandleThreadDumperUnavailable() { - Mockito.when(this.delegateMock.threadDump()) - .thenThrow(new ThreadDumperUnavailableException("No thread dumper available")); - WebEndpointResponse response = this.extension.threadDump(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); - } - - @Test - void shouldHandleThreadDumperUnavailableText() { - Mockito.when(this.delegateMock.textThreadDump()) - .thenThrow(new ThreadDumperUnavailableException("No thread dumper available")); - WebEndpointResponse response = this.extension.textThreadDump(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); - } - -}