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
pull/21605/head
Andy Wilkinson 5 years ago
parent b5673db6fa
commit f17f1255a4

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

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

@ -24,6 +24,7 @@ import java.util.List;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState; import org.springframework.boot.availability.ReadinessState;
@ -141,6 +142,18 @@ class ReactiveWebServerApplicationContextTests {
.isEqualTo(ReadinessState.REFUSING_TRAFFIC); .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 @Test
void whenTheContextIsRefreshedThenASubsequentRefreshAttemptWillFail() { void whenTheContextIsRefreshedThenASubsequentRefreshAttemptWillFail() {
addWebServerFactoryBean(); addWebServerFactoryBean();
@ -186,4 +199,12 @@ class ReactiveWebServerApplicationContextTests {
} }
static class RefreshFailure {
RefreshFailure() {
throw new RuntimeException("Fail refresh");
}
}
} }

@ -182,6 +182,17 @@ class ServletWebServerApplicationContextTests {
ContextClosedEvent.class); 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 @Test
void cannotSecondRefresh() { void cannotSecondRefresh() {
addWebServerFactoryBean(); addWebServerFactoryBean();
@ -531,4 +542,12 @@ class ServletWebServerApplicationContextTests {
} }
static class RefreshFailure {
RefreshFailure() {
throw new RuntimeException("Fail refresh");
}
}
} }

Loading…
Cancel
Save