Skip to content

Commit 9cb1569

Browse files
committed
Synchronize message sending
Issue: SPR-12516 (backport for commit b796c1)
1 parent ac5c361 commit 9cb1569

File tree

4 files changed

+50
-51
lines changed

4 files changed

+50
-51
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java

+37-35
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
7070

7171
private final Object responseLock = new Object();
7272

73-
private volatile boolean requestInitialized;
73+
private volatile boolean readyToSend;
7474

7575

7676
private final Queue<String> messageCache;
@@ -195,18 +195,24 @@ public void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse r
195195
this.localAddress = request.getLocalAddress();
196196
this.remoteAddress = request.getRemoteAddress();
197197

198-
this.response = response;
199-
this.frameFormat = frameFormat;
200-
this.asyncRequestControl = request.getAsyncRequestControl(response);
201-
202198
synchronized (this.responseLock) {
203199
try {
200+
this.response = response;
201+
this.frameFormat = frameFormat;
202+
this.asyncRequestControl = request.getAsyncRequestControl(response);
203+
this.asyncRequestControl.start(-1);
204+
204205
// Let "our" handler know before sending the open frame to the remote handler
205206
delegateConnectionEstablished();
206-
writePrelude(request, response);
207-
writeFrame(SockJsFrame.openFrame());
208-
if (isStreaming() && !isClosed()) {
209-
startAsyncRequest();
207+
208+
if (isStreaming()) {
209+
writePrelude(request, response);
210+
writeFrame(SockJsFrame.openFrame());
211+
flushCache();
212+
this.readyToSend = true;
213+
}
214+
else {
215+
writeFrame(SockJsFrame.openFrame());
210216
}
211217
}
212218
catch (Throwable ex) {
@@ -219,17 +225,6 @@ public void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse r
219225
protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
220226
}
221227

222-
private void startAsyncRequest() {
223-
this.asyncRequestControl.start(-1);
224-
if (this.messageCache.size() > 0) {
225-
flushCache();
226-
}
227-
else {
228-
scheduleHeartbeat();
229-
}
230-
this.requestInitialized = true;
231-
}
232-
233228
/**
234229
* Handle all requests, except the first one, to receive messages on a SockJS
235230
* HTTP transport based session.
@@ -249,12 +244,27 @@ public void handleSuccessiveRequest(ServerHttpRequest request, ServerHttpRespons
249244
try {
250245
if (isClosed()) {
251246
response.getBody().write(SockJsFrame.closeFrameGoAway().getContentBytes());
247+
return;
252248
}
253249
this.response = response;
254250
this.frameFormat = frameFormat;
255251
this.asyncRequestControl = request.getAsyncRequestControl(response);
256-
writePrelude(request, response);
257-
startAsyncRequest();
252+
this.asyncRequestControl.start(-1);
253+
254+
if (isStreaming()) {
255+
writePrelude(request, response);
256+
flushCache();
257+
this.readyToSend = true;
258+
}
259+
else {
260+
if (this.messageCache.isEmpty()) {
261+
scheduleHeartbeat();
262+
this.readyToSend = true;
263+
}
264+
else {
265+
flushCache();
266+
}
267+
}
258268
}
259269
catch (Throwable ex) {
260270
tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
@@ -266,32 +276,24 @@ public void handleSuccessiveRequest(ServerHttpRequest request, ServerHttpRespons
266276

267277
@Override
268278
protected final void sendMessageInternal(String message) throws SockJsTransportFailureException {
269-
this.messageCache.add(message);
270-
tryFlushCache();
271-
}
272-
273-
private boolean tryFlushCache() throws SockJsTransportFailureException {
274279
synchronized (this.responseLock) {
275-
if (this.messageCache.isEmpty()) {
276-
logger.trace("Nothing to flush in session=" + this.getId());
277-
return false;
278-
}
280+
this.messageCache.add(message);
279281
if (logger.isTraceEnabled()) {
280282
logger.trace(this.messageCache.size() + " message(s) to flush in session " + this.getId());
281283
}
282-
if (isActive() && this.requestInitialized) {
284+
if (isActive() && this.readyToSend) {
283285
if (logger.isTraceEnabled()) {
284286
logger.trace("Session is active, ready to flush.");
285287
}
286288
cancelHeartbeat();
287289
flushCache();
288-
return true;
290+
return;
289291
}
290292
else {
291293
if (logger.isTraceEnabled()) {
292294
logger.trace("Session is not active, not ready to flush.");
293295
}
294-
return false;
296+
return;
295297
}
296298
}
297299
}
@@ -312,7 +314,7 @@ protected void resetRequest() {
312314

313315
ServerHttpAsyncRequestControl control = this.asyncRequestControl;
314316
this.asyncRequestControl = null;
315-
this.requestInitialized = false;
317+
this.readyToSend = false;
316318
this.response = null;
317319

318320
updateLastActiveTime();

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/StreamingSockJsSession.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@
1818

1919
import java.util.Map;
2020

21-
import org.springframework.http.server.ServerHttpRequest;
22-
import org.springframework.http.server.ServerHttpResponse;
2321
import org.springframework.web.socket.WebSocketHandler;
24-
import org.springframework.web.socket.sockjs.SockJsException;
2522
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
2623
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
27-
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
2824
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
2925
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
3026

@@ -53,7 +49,7 @@ protected boolean isStreaming() {
5349

5450
@Override
5551
protected void flushCache() throws SockJsTransportFailureException {
56-
do {
52+
while (!getMessageCache().isEmpty()) {
5753
String message = getMessageCache().poll();
5854
SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec();
5955
SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, message);
@@ -73,7 +69,6 @@ protected void flushCache() throws SockJsTransportFailureException {
7369
break;
7470
}
7571
}
76-
while (!getMessageCache().isEmpty());
7772
scheduleHeartbeat();
7873
}
7974

spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -16,25 +16,27 @@
1616

1717
package org.springframework.web.socket.sockjs.transport.handler;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
22+
import static org.mockito.Matchers.any;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.verify;
25+
1926
import java.sql.Date;
2027

2128
import org.junit.Before;
2229
import org.junit.Test;
23-
2430
import org.springframework.scheduling.TaskScheduler;
2531
import org.springframework.web.socket.AbstractHttpRequestTests;
2632
import org.springframework.web.socket.WebSocketHandler;
27-
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
2833
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
34+
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
2935
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
3036
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
3137
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
3238
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
3339

34-
import static org.junit.Assert.*;
35-
import static org.mockito.Matchers.any;
36-
import static org.mockito.Mockito.*;
37-
3840
/**
3941
* Test fixture for {@link AbstractHttpSendingTransportHandler} and sub-classes.
4042
*
@@ -75,7 +77,7 @@ public void handleRequestXhr() throws Exception {
7577
assertFalse("Polling request should complete after open frame", this.servletRequest.isAsyncStarted());
7678
verify(this.webSocketHandler).afterConnectionEstablished(session);
7779

78-
resetResponse();
80+
resetRequestAndResponse();
7981
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
8082

8183
assertTrue("Polling request should remain open", this.servletRequest.isAsyncStarted());

spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/HttpSockJsSessionTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public void handleInitialRequest() throws Exception {
8282
this.session.handleInitialRequest(this.request, this.response, this.frameFormat);
8383

8484
assertEquals("hhh\no", this.servletResponse.getContentAsString());
85-
assertFalse(this.servletRequest.isAsyncStarted());
85+
assertTrue(this.servletRequest.isAsyncStarted());
8686

8787
verify(this.webSocketHandler).afterConnectionEstablished(this.session);
8888
}
@@ -119,7 +119,7 @@ public TestAbstractHttpSockJsSession(SockJsServiceConfig config, WebSocketHandle
119119

120120
@Override
121121
protected boolean isStreaming() {
122-
return false;
122+
return true;
123123
}
124124

125125
@Override

0 commit comments

Comments
 (0)