Skip to content

Commit 84998f9

Browse files
committed
Merge pull request #44310 from nosan
* pr/44310: Polish contribution Catch WebServer stop or destroy exception when context refresh fails Closes gh-44310
2 parents e51efef + 062b73f commit 84998f9

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@ public final void refresh() throws BeansException, IllegalStateException {
6868
catch (RuntimeException ex) {
6969
WebServer webServer = getWebServer();
7070
if (webServer != null) {
71-
webServer.stop();
72-
webServer.destroy();
71+
try {
72+
webServer.stop();
73+
webServer.destroy();
74+
}
75+
catch (RuntimeException stopOrDestroyEx) {
76+
ex.addSuppressed(stopOrDestroyEx);
77+
}
7378
}
7479
throw ex;
7580
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,15 @@ public final void refresh() throws BeansException, IllegalStateException {
146146
super.refresh();
147147
}
148148
catch (RuntimeException ex) {
149-
WebServer webServer = this.webServer;
150-
if (webServer != null) {
151-
webServer.stop();
152-
webServer.destroy();
149+
try {
150+
WebServer webServer = this.webServer;
151+
if (webServer != null) {
152+
webServer.stop();
153+
webServer.destroy();
154+
}
155+
}
156+
catch (RuntimeException stopOrDestroyEx) {
157+
ex.addSuppressed(stopOrDestroyEx);
153158
}
154159
throw ex;
155160
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java

+35
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4444
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4545
import static org.mockito.BDDMockito.then;
46+
import static org.mockito.BDDMockito.willThrow;
4647
import static org.mockito.Mockito.times;
4748

4849
/**
@@ -133,6 +134,40 @@ void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyed() {
133134
then(webServer).should().destroy();
134135
}
135136

137+
@Test
138+
void whenContextRefreshFailedThenWebServerStopFailedCatchStopException() {
139+
addWebServerFactoryBean();
140+
addHttpHandlerBean();
141+
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
142+
willThrow(new RuntimeException("WebServer has failed to stop")).willCallRealMethod()
143+
.given(this.context.getWebServer())
144+
.stop();
145+
return new RefreshFailure();
146+
}));
147+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
148+
.withStackTraceContaining("WebServer has failed to stop");
149+
WebServer webServer = this.context.getWebServer();
150+
then(webServer).should().stop();
151+
then(webServer).should(times(0)).destroy();
152+
}
153+
154+
@Test
155+
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyFailedCatchDestroyException() {
156+
addWebServerFactoryBean();
157+
addHttpHandlerBean();
158+
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
159+
willThrow(new RuntimeException("WebServer has failed to destroy")).willCallRealMethod()
160+
.given(this.context.getWebServer())
161+
.destroy();
162+
return new RefreshFailure();
163+
}));
164+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
165+
.withStackTraceContaining("WebServer has failed to destroy");
166+
WebServer webServer = this.context.getWebServer();
167+
then(webServer).should().stop();
168+
then(webServer).should().destroy();
169+
}
170+
136171
@Test
137172
void whenContextIsClosedThenWebServerIsStoppedAndDestroyed() {
138173
addWebServerFactoryBean();

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@
5151
import org.springframework.boot.testsupport.system.CapturedOutput;
5252
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
5353
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
54+
import org.springframework.boot.web.server.WebServer;
5455
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
5556
import org.springframework.boot.web.servlet.FilterRegistrationBean;
5657
import org.springframework.boot.web.servlet.ServletContextInitializer;
@@ -81,6 +82,7 @@
8182
import static org.mockito.ArgumentMatchers.anyString;
8283
import static org.mockito.BDDMockito.given;
8384
import static org.mockito.BDDMockito.then;
85+
import static org.mockito.BDDMockito.willThrow;
8486
import static org.mockito.Mockito.atMost;
8587
import static org.mockito.Mockito.inOrder;
8688
import static org.mockito.Mockito.mock;
@@ -211,6 +213,48 @@ void whenContextIsNotActiveThenCloseDoesNotChangeTheApplicationAvailability() {
211213
assertThat(listener.receivedEvents()).isEmpty();
212214
}
213215

216+
@Test
217+
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyed() {
218+
addWebServerFactoryBean();
219+
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class));
220+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh);
221+
WebServer webServer = this.context.getWebServer();
222+
then(webServer).should(times(2)).stop();
223+
then(webServer).should().destroy();
224+
}
225+
226+
@Test
227+
void whenContextRefreshFailedThenWebServerStopFailedCatchStopException() {
228+
addWebServerFactoryBean();
229+
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
230+
willThrow(new RuntimeException("WebServer has failed to stop")).willCallRealMethod()
231+
.given(this.context.getWebServer())
232+
.stop();
233+
return new RefreshFailure();
234+
}));
235+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
236+
.withStackTraceContaining("WebServer has failed to stop");
237+
WebServer webServer = this.context.getWebServer();
238+
then(webServer).should().stop();
239+
then(webServer).should(times(0)).destroy();
240+
}
241+
242+
@Test
243+
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyFailedCatchDestroyException() {
244+
addWebServerFactoryBean();
245+
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
246+
willThrow(new RuntimeException("WebServer has failed to destroy")).willCallRealMethod()
247+
.given(this.context.getWebServer())
248+
.destroy();
249+
return new RefreshFailure();
250+
}));
251+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
252+
.withStackTraceContaining("WebServer has failed to destroy");
253+
WebServer webServer = this.context.getWebServer();
254+
then(webServer).should().stop();
255+
then(webServer).should().destroy();
256+
}
257+
214258
@Test
215259
void cannotSecondRefresh() {
216260
addWebServerFactoryBean();

0 commit comments

Comments
 (0)