From 9c68a2ab876e5011718dbe6abb8f5902f7031deb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 22 Nov 2023 19:19:38 +0000 Subject: [PATCH] Integrate child management context with parent context's lifecycle Previously, the child management context was created when the parent context's web server was initialized and it wasn't stopped or closed until the parent context was closed. This resulted in the child context being left running when the parent context was stopped. This would then cause a failure when the parent context was started again as another web server initialized event would be received and a second child management context would be started. This commit updates the initialization of the child management context to integrate it with the lifecycle of the parent context. The management context is now created the first time the parent context is started. It is stopped when the parent context is stopped and restarted if the parent context is started again. This lifecycle management is done using a phase that ensures that the child context is not started until the parent context's web server has been started. Fixes gh-38502 --- .../ChildManagementContextInitializer.java | 33 ++++++++++++++++--- ...nagementContextAutoConfigurationTests.java | 15 +++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java index 38a6a68f36e5..579e7d7126d5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java @@ -33,12 +33,13 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; -import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.AnnotationConfigRegistry; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.event.ContextClosedEvent; @@ -55,8 +56,7 @@ * @author Andy Wilkinson * @author Phillip Webb */ -class ChildManagementContextInitializer - implements ApplicationListener, BeanRegistrationAotProcessor { +class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, SmartLifecycle { private final ManagementContextFactory managementContextFactory; @@ -64,6 +64,8 @@ class ChildManagementContextInitializer private final ApplicationContextInitializer applicationContextInitializer; + private volatile ConfigurableApplicationContext managementContext; + ChildManagementContextInitializer(ManagementContextFactory managementContextFactory, ApplicationContext parentContext) { this(managementContextFactory, parentContext, null); @@ -79,14 +81,35 @@ private ChildManagementContextInitializer(ManagementContextFactory managementCon } @Override - public void onApplicationEvent(WebServerInitializedEvent event) { - if (event.getApplicationContext().equals(this.parentContext)) { + public void start() { + if (this.managementContext == null) { ConfigurableApplicationContext managementContext = createManagementContext(); registerBeans(managementContext); managementContext.refresh(); + this.managementContext = managementContext; + } + else { + this.managementContext.start(); + } + } + + @Override + public void stop() { + if (this.managementContext != null) { + this.managementContext.stop(); } } + @Override + public boolean isRunning() { + return this.managementContext != null && this.managementContext.isRunning(); + } + + @Override + public int getPhase() { + return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE + 512; + } + @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { Assert.isInstanceOf(ConfigurableApplicationContext.class, this.parentContext); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java index 1d4e7c731983..52fca8b17ae8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java @@ -55,6 +55,21 @@ void childManagementContextShouldStartForEmbeddedServer(CapturedOutput output) { .run((context) -> assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2))); } + @Test + void childManagementContextShouldRestartWhenParentIsStoppedThenStarted(CapturedOutput output) { + WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, + WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class)); + contextRunner.withPropertyValues("server.port=0", "management.server.port=0").run((context) -> { + assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2)); + context.getSourceApplicationContext().stop(); + context.getSourceApplicationContext().start(); + assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 4)); + }); + } + @Test void givenSamePortManagementServerWhenManagementServerAddressIsConfiguredThenContextRefreshFails() { WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(