Skip to content

Commit 885e76b

Browse files
committed
Synchronized lazy start in JettyRequestUpgradeStrategy
Issue: SPR-14527
1 parent 7542278 commit 885e76b

File tree

3 files changed

+56
-38
lines changed

3 files changed

+56
-38
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy;
3838
import org.springframework.web.server.ServerWebExchange;
3939

40+
4041
/**
4142
* A {@link RequestUpgradeStrategy} for use with Jetty.
4243
*
@@ -45,52 +46,58 @@
4546
*/
4647
public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Lifecycle {
4748

48-
private static final ThreadLocal<JettyWebSocketHandlerAdapter> wsContainerHolder =
49-
new NamedThreadLocal<>("Jetty WebSocketHandler Adapter");
49+
private static final ThreadLocal<JettyWebSocketHandlerAdapter> adapterHolder =
50+
new NamedThreadLocal<>("JettyWebSocketHandlerAdapter");
5051

5152

5253
private WebSocketServerFactory factory;
5354

5455
private ServletContext servletContext;
5556

56-
private volatile boolean running = false;
57+
private boolean running = false;
58+
59+
private final Object lifecycleMonitor = new Object();
5760

5861

5962
@Override
6063
public void start() {
61-
if (!isRunning() && this.servletContext != null) {
62-
this.running = true;
63-
try {
64-
this.factory = new WebSocketServerFactory(this.servletContext);
65-
this.factory.setCreator((request, response) -> {
66-
JettyWebSocketHandlerAdapter adapter = wsContainerHolder.get();
67-
Assert.state(adapter != null, "Expected JettyWebSocketHandlerAdapter");
68-
return adapter;
69-
});
70-
this.factory.start();
71-
}
72-
catch (Exception ex) {
73-
throw new IllegalStateException("Unable to start Jetty WebSocketServerFactory", ex);
64+
synchronized (this.lifecycleMonitor) {
65+
if (!isRunning() && this.servletContext != null) {
66+
this.running = true;
67+
try {
68+
this.factory = new WebSocketServerFactory(this.servletContext);
69+
this.factory.setCreator((request, response) -> adapterHolder.get());
70+
this.factory.start();
71+
}
72+
catch (Exception ex) {
73+
throw new IllegalStateException("Unable to start WebSocketServerFactory", ex);
74+
}
7475
}
7576
}
7677
}
7778

7879
@Override
7980
public void stop() {
80-
if (isRunning()) {
81-
this.running = false;
82-
try {
83-
this.factory.stop();
84-
}
85-
catch (Exception ex) {
86-
throw new IllegalStateException("Unable to stop Jetty WebSocketServerFactory", ex);
81+
synchronized (this.lifecycleMonitor) {
82+
if (isRunning()) {
83+
try {
84+
this.factory.stop();
85+
}
86+
catch (Exception ex) {
87+
throw new IllegalStateException("Failed to stop WebSocketServerFactory", ex);
88+
}
89+
finally {
90+
this.running = false;
91+
}
8792
}
8893
}
8994
}
9095

9196
@Override
9297
public boolean isRunning() {
93-
return this.running;
98+
synchronized (this.lifecycleMonitor) {
99+
return this.running;
100+
}
94101
}
95102

96103
@Override
@@ -103,25 +110,20 @@ public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler)
103110
HttpServletRequest servletRequest = getHttpServletRequest(request);
104111
HttpServletResponse servletResponse = getHttpServletResponse(response);
105112

106-
if (this.servletContext == null) {
107-
this.servletContext = servletRequest.getServletContext();
108-
this.servletContext.setAttribute(DecoratedObjectFactory.ATTR, new DecoratedObjectFactory());
109-
}
110-
111-
try {
112-
start();
113+
startLazily(servletRequest);
113114

114-
Assert.isTrue(this.factory.isUpgradeRequest(
115-
servletRequest, servletResponse), "Not a WebSocket handshake");
115+
boolean isUpgrade = this.factory.isUpgradeRequest(servletRequest, servletResponse);
116+
Assert.isTrue(isUpgrade, "Not a WebSocket handshake");
116117

117-
wsContainerHolder.set(adapter);
118+
try {
119+
adapterHolder.set(adapter);
118120
this.factory.acceptWebSocket(servletRequest, servletResponse);
119121
}
120122
catch (IOException ex) {
121123
return Mono.error(ex);
122124
}
123125
finally {
124-
wsContainerHolder.remove();
126+
adapterHolder.remove();
125127
}
126128

127129
return Mono.empty();
@@ -137,4 +139,17 @@ private HttpServletResponse getHttpServletResponse(ServerHttpResponse response)
137139
return ((ServletServerHttpResponse) response).getServletResponse();
138140
}
139141

142+
private void startLazily(HttpServletRequest request) {
143+
if (this.servletContext != null) {
144+
return;
145+
}
146+
synchronized (this.lifecycleMonitor) {
147+
if (this.servletContext == null) {
148+
this.servletContext = request.getServletContext();
149+
this.servletContext.setAttribute(DecoratedObjectFactory.ATTR, new DecoratedObjectFactory());
150+
start();
151+
}
152+
}
153+
}
154+
140155
}

spring-web-reactive/src/test/java/org/springframework/web/reactive/socket/server/AbstractWebSocketHandlerIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public abstract class AbstractWebSocketHandlerIntegrationTests {
6666
public Class<?> handlerAdapterConfigClass;
6767

6868

69-
@Parameters
69+
@Parameters(name = "server [{0}]")
7070
public static Object[][] arguments() {
7171
File base = new File(System.getProperty("java.io.tmpdir"));
7272
return new Object[][] {

spring-web-reactive/src/test/java/org/springframework/web/reactive/socket/server/BasicWebSocketHandlerIntegrationTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23-
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
2423
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
2524
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
2625
import io.reactivex.netty.protocol.http.client.HttpClient;
@@ -66,7 +65,11 @@ public void echo() throws Exception {
6665
.mergeWith(conn.getInput())
6766
)
6867
.take(10)
69-
.map(frame -> frame.content().toString(StandardCharsets.UTF_8))
68+
.map(frame -> {
69+
String text = frame.content().toString(StandardCharsets.UTF_8);
70+
frame.release();
71+
return text;
72+
})
7073
.toList().toBlocking().first();
7174
List<String> expected = messages.toList().toBlocking().first();
7275
assertEquals(expected, actual);

0 commit comments

Comments
 (0)