Do not change availability on close unless context is active

Previously, an AvailabilityChangeEvent was published when the servlet
and reactive web server application contexts were closed, irrespective
of whether or not the context was active. This caused problems when
the context was not active due to a refresh failure as the event
publication could then trigger bean creation and post-processing that
relied upon beans that had been destroyed when cleaning up after the
refresh failure. The most commonly seen symptom was a missing
importRegistry bean that is required by ImportAwareBeanPostProcessor.

This commit updates the two web server application contexts to only
publish the availability change event if the context is active.

Fixes gh-21588
This commit is contained in:
Andy Wilkinson 2020-05-27 14:43:12 +01:00
parent b5673db6fa
commit f17f1255a4
4 changed files with 46 additions and 2 deletions

View File

@ -136,7 +136,9 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
@Override
protected void doClose() {
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
if (this.isActive()) {
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
}
super.doClose();
}

View File

@ -164,7 +164,9 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon
@Override
protected void doClose() {
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
if (this.isActive()) {
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
}
super.doClose();
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
@ -141,6 +142,18 @@ class ReactiveWebServerApplicationContextTests {
.isEqualTo(ReadinessState.REFUSING_TRAFFIC);
}
@Test
void whenContextIsNotActiveThenCloseDoesNotChangeTheApplicationAvailability() {
addWebServerFactoryBean();
addHttpHandlerBean();
TestApplicationListener listener = new TestApplicationListener();
this.context.addApplicationListener(listener);
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh);
this.context.close();
assertThat(listener.receivedEvents()).isEmpty();
}
@Test
void whenTheContextIsRefreshedThenASubsequentRefreshAttemptWillFail() {
addWebServerFactoryBean();
@ -186,4 +199,12 @@ class ReactiveWebServerApplicationContextTests {
}
static class RefreshFailure {
RefreshFailure() {
throw new RuntimeException("Fail refresh");
}
}
}

View File

@ -182,6 +182,17 @@ class ServletWebServerApplicationContextTests {
ContextClosedEvent.class);
}
@Test
void whenContextIsNotActiveThenCloseDoesNotChangeTheApplicationAvailability() {
addWebServerFactoryBean();
TestApplicationListener listener = new TestApplicationListener();
this.context.addApplicationListener(listener);
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh);
this.context.close();
assertThat(listener.receivedEvents()).isEmpty();
}
@Test
void cannotSecondRefresh() {
addWebServerFactoryBean();
@ -531,4 +542,12 @@ class ServletWebServerApplicationContextTests {
}
static class RefreshFailure {
RefreshFailure() {
throw new RuntimeException("Fail refresh");
}
}
}