Skip to content

Commit 8c896d9

Browse files
committed
Improve Reactive...Context thread safety
Refactor `ReactiveWebServerApplicationContext` to improve thread safety by using a single manager object rather than then trying to synchronize the `WebServer` and `HttpHandler`. Closes gh-14666
1 parent cf24d18 commit 8c896d9

File tree

2 files changed

+76
-68
lines changed

2 files changed

+76
-68
lines changed

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

Lines changed: 73 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ public class ReactiveWebServerApplicationContext
4242
extends GenericReactiveWebApplicationContext
4343
implements ConfigurableWebServerApplicationContext {
4444

45-
private volatile WebServer webServer;
46-
47-
private volatile DeferredHttpHandler httpHandler;
45+
private volatile ServerManager serverManager;
4846

4947
private String serverNamespace;
5048

@@ -87,41 +85,13 @@ protected void onRefresh() {
8785
}
8886

8987
private void createWebServer() {
90-
WebServer localServer = this.webServer;
91-
if (localServer == null) {
92-
DeferredHttpHandler localHandler = new DeferredHttpHandler(
93-
this::getHttpHandler);
94-
this.webServer = getWebServerFactory().getWebServer(localHandler);
95-
this.httpHandler = localHandler;
88+
ServerManager serverManager = this.serverManager;
89+
if (serverManager == null) {
90+
this.serverManager = ServerManager.get(getWebServerFactory());
9691
}
9792
initPropertySources();
9893
}
9994

100-
@Override
101-
protected void finishRefresh() {
102-
super.finishRefresh();
103-
WebServer localServer = startReactiveWebServer();
104-
if (localServer != null) {
105-
publishEvent(new ReactiveWebServerInitializedEvent(localServer, this));
106-
}
107-
}
108-
109-
@Override
110-
protected void onClose() {
111-
super.onClose();
112-
stopAndReleaseReactiveWebServer();
113-
}
114-
115-
/**
116-
* Returns the {@link WebServer} that was created by the context or {@code null} if
117-
* the server has not yet been created.
118-
* @return the web server
119-
*/
120-
@Override
121-
public WebServer getWebServer() {
122-
return this.webServer;
123-
}
124-
12595
/**
12696
* Return the {@link ReactiveWebServerFactory} that should be used to create the
12797
* reactive web server. By default this method searches for a suitable bean in the
@@ -146,6 +116,21 @@ protected ReactiveWebServerFactory getWebServerFactory() {
146116
return getBeanFactory().getBean(beanNames[0], ReactiveWebServerFactory.class);
147117
}
148118

119+
@Override
120+
protected void finishRefresh() {
121+
super.finishRefresh();
122+
WebServer webServer = startReactiveWebServer();
123+
if (webServer != null) {
124+
publishEvent(new ReactiveWebServerInitializedEvent(webServer, this));
125+
}
126+
}
127+
128+
private WebServer startReactiveWebServer() {
129+
ServerManager serverManager = this.serverManager;
130+
ServerManager.start(serverManager, this::getHttpHandler);
131+
return ServerManager.getWebServer(serverManager);
132+
}
133+
149134
/**
150135
* Return the {@link HttpHandler} that should be used to process the reactive web
151136
* server. By default this method searches for a suitable bean in the context itself.
@@ -166,32 +151,32 @@ protected HttpHandler getHttpHandler() {
166151
return getBeanFactory().getBean(beanNames[0], HttpHandler.class);
167152
}
168153

169-
private WebServer startReactiveWebServer() {
170-
WebServer localServer = this.webServer;
171-
DeferredHttpHandler localHandler = this.httpHandler;
172-
if (localServer != null) {
173-
if (localHandler != null) {
174-
localHandler.initialize();
175-
this.httpHandler = null;
176-
}
177-
localServer.start();
178-
}
179-
return localServer;
154+
@Override
155+
protected void onClose() {
156+
super.onClose();
157+
stopAndReleaseReactiveWebServer();
180158
}
181159

182160
private void stopAndReleaseReactiveWebServer() {
183-
WebServer localServer = this.webServer;
184-
if (localServer != null) {
185-
try {
186-
localServer.stop();
187-
this.webServer = null;
188-
}
189-
catch (Exception ex) {
190-
throw new IllegalStateException(ex);
191-
}
161+
ServerManager serverManager = this.serverManager;
162+
try {
163+
ServerManager.stop(serverManager);
164+
}
165+
finally {
166+
this.serverManager = null;
192167
}
193168
}
194169

170+
/**
171+
* Returns the {@link WebServer} that was created by the context or {@code null} if
172+
* the server has not yet been created.
173+
* @return the web server
174+
*/
175+
@Override
176+
public WebServer getWebServer() {
177+
return ServerManager.getWebServer(this.serverManager);
178+
}
179+
195180
@Override
196181
public String getServerNamespace() {
197182
return this.serverNamespace;
@@ -203,22 +188,18 @@ public void setServerNamespace(String serverNamespace) {
203188
}
204189

205190
/**
206-
* {@link HttpHandler} that defers to a supplied handler which is initialized only
207-
* when the server starts.
191+
* Internal class used to manage the server and the {@link HttpHandler}, taking care
192+
* not to initialize the hander too early.
208193
*/
209-
static class DeferredHttpHandler implements HttpHandler {
194+
static final class ServerManager implements HttpHandler {
210195

211-
private Supplier<HttpHandler> factory;
196+
private final WebServer server;
212197

213-
private HttpHandler handler;
198+
private volatile HttpHandler handler;
214199

215-
DeferredHttpHandler(Supplier<HttpHandler> factory) {
216-
this.factory = factory;
200+
private ServerManager(ReactiveWebServerFactory factory) {
217201
this.handler = this::handleUninitialized;
218-
}
219-
220-
public void initialize() {
221-
this.handler = this.factory.get();
202+
this.server = factory.getWebServer(this);
222203
}
223204

224205
private Mono<Void> handleUninitialized(ServerHttpRequest request,
@@ -236,6 +217,33 @@ public HttpHandler getHandler() {
236217
return this.handler;
237218
}
238219

220+
public static ServerManager get(ReactiveWebServerFactory factory) {
221+
return new ServerManager(factory);
222+
}
223+
224+
public static WebServer getWebServer(ServerManager manager) {
225+
return (manager != null) ? manager.server : null;
226+
}
227+
228+
public static void start(ServerManager manager,
229+
Supplier<HttpHandler> handlerSupplier) {
230+
if (manager != null) {
231+
manager.handler = handlerSupplier.get();
232+
manager.server.start();
233+
}
234+
}
235+
236+
public static void stop(ServerManager manager) {
237+
if (manager != null) {
238+
try {
239+
manager.server.stop();
240+
}
241+
catch (Exception ex) {
242+
throw new IllegalStateException(ex);
243+
}
244+
}
245+
}
246+
239247
}
240248

241249
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import org.junit.Test;
2020

21-
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.DeferredHttpHandler;
21+
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.ServerManager;
2222
import org.springframework.boot.web.reactive.context.config.ExampleReactiveWebServerApplicationConfiguration;
2323
import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory;
2424
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
@@ -98,8 +98,8 @@ private void verifyContext() {
9898
.getBean(MockReactiveWebServerFactory.class);
9999
HttpHandler expectedHandler = this.context.getBean(HttpHandler.class);
100100
HttpHandler actualHandler = factory.getWebServer().getHttpHandler();
101-
if (actualHandler instanceof DeferredHttpHandler) {
102-
actualHandler = ((DeferredHttpHandler) actualHandler).getHandler();
101+
if (actualHandler instanceof ServerManager) {
102+
actualHandler = ((ServerManager) actualHandler).getHandler();
103103
}
104104
assertThat(actualHandler).isEqualTo(expectedHandler);
105105
}

0 commit comments

Comments
 (0)