Skip to content

Commit

Permalink
Integrate child management context with parent context's lifecycle
Browse files Browse the repository at this point in the history
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 spring-projectsgh-38502
  • Loading branch information
wilkinsona committed Nov 22, 2023
1 parent de8b304 commit 9c68a2a
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -55,15 +56,16 @@
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ChildManagementContextInitializer
implements ApplicationListener<WebServerInitializedEvent>, BeanRegistrationAotProcessor {
class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, SmartLifecycle {

private final ManagementContextFactory managementContextFactory;

private final ApplicationContext parentContext;

private final ApplicationContextInitializer<ConfigurableApplicationContext> applicationContextInitializer;

private volatile ConfigurableApplicationContext managementContext;

ChildManagementContextInitializer(ManagementContextFactory managementContextFactory,
ApplicationContext parentContext) {
this(managementContextFactory, parentContext, null);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 9c68a2a

Please sign in to comment.