Skip to content

Commit 8d11247

Browse files
committed
Catch WebServer stop or destroy exception when context refresh fails
This commit adds the stop or destroy failure as a suppressed exception if either ServletWebServerApplicationContext or ReactiveWebServerApplicationContext refresh fails. Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
1 parent e51efef commit 8d11247

File tree

4 files changed

+102
-11
lines changed

4 files changed

+102
-11
lines changed

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,18 @@ public final void refresh() throws BeansException, IllegalStateException {
6565
try {
6666
super.refresh();
6767
}
68-
catch (RuntimeException ex) {
68+
catch (RuntimeException refreshEx) {
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+
refreshEx.addSuppressed(stopOrDestroyEx);
77+
}
7378
}
74-
throw ex;
79+
throw refreshEx;
7580
}
7681
}
7782

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

+11-6
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,18 @@ public final void refresh() throws BeansException, IllegalStateException {
145145
try {
146146
super.refresh();
147147
}
148-
catch (RuntimeException ex) {
149-
WebServer webServer = this.webServer;
150-
if (webServer != null) {
151-
webServer.stop();
152-
webServer.destroy();
148+
catch (RuntimeException refreshEx) {
149+
try {
150+
WebServer webServer = this.webServer;
151+
if (webServer != null) {
152+
webServer.stop();
153+
webServer.destroy();
154+
}
153155
}
154-
throw ex;
156+
catch (RuntimeException stopOrDestroyEx) {
157+
refreshEx.addSuppressed(stopOrDestroyEx);
158+
}
159+
throw refreshEx;
155160
}
156161
}
157162

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

+47-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,50 @@ void whenContextIsNotActiveThenCloseDoesNotChangeTheApplicationAvailability() {
211213
assertThat(listener.receivedEvents()).isEmpty();
212214
}
213215

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

0 commit comments

Comments
 (0)