Skip to content

Commit 935ffc5

Browse files
committed
Fine-tuned concurrency and general polishing in SSE support classes
Issue: SPR-12212
1 parent 4e1af7d commit 935ffc5

File tree

3 files changed

+18
-29
lines changed

3 files changed

+18
-29
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.web.servlet.mvc.method.annotation;
1718

1819
import java.io.IOException;
@@ -23,7 +24,6 @@
2324
import org.springframework.http.server.ServerHttpResponse;
2425
import org.springframework.util.Assert;
2526

26-
2727
/**
2828
* A controller method return value type for asynchronous request processing
2929
* where one or more objects are written to the response. While
@@ -53,15 +53,12 @@
5353
* emitter.complete();
5454
* </pre>
5555
*
56-
* <p><strong>Note:</strong> this class is not thread-safe. Callers must ensure
57-
* that use from multiple threads is synchronized.
58-
*
5956
* @author Rossen Stoyanchev
6057
* @since 4.2
6158
*/
6259
public class ResponseBodyEmitter {
6360

64-
private Handler handler;
61+
private volatile Handler handler;
6562

6663
/* Cache for objects sent before handler is set. */
6764
private final Map<Object, MediaType> initHandlerCache = new LinkedHashMap<Object, MediaType>(10);
@@ -124,17 +121,17 @@ public void send(Object object) throws IOException {
124121
* @throws java.lang.IllegalStateException wraps any other errors
125122
*/
126123
public void send(Object object, MediaType mediaType) throws IOException {
127-
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete.");
124+
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete");
128125
sendInternal(object, mediaType);
129126
}
130127

131128
private void sendInternal(Object object, MediaType mediaType) throws IOException {
132129
if (object == null) {
133130
return;
134131
}
135-
if (handler == null) {
132+
if (this.handler == null) {
136133
synchronized (this) {
137-
if (handler == null) {
134+
if (this.handler == null) {
138135
this.initHandlerCache.put(object, mediaType);
139136
return;
140137
}
@@ -143,11 +140,11 @@ private void sendInternal(Object object, MediaType mediaType) throws IOException
143140
try {
144141
this.handler.send(object, mediaType);
145142
}
146-
catch(IOException ex){
143+
catch (IOException ex){
147144
this.handler.completeWithError(ex);
148145
throw ex;
149146
}
150-
catch(Throwable ex){
147+
catch (Throwable ex){
151148
this.handler.completeWithError(ex);
152149
throw new IllegalStateException("Failed to send " + object, ex);
153150
}
@@ -161,7 +158,7 @@ private void sendInternal(Object object, MediaType mediaType) throws IOException
161158
public void complete() {
162159
synchronized (this) {
163160
this.complete = true;
164-
if (handler != null) {
161+
if (this.handler != null) {
165162
this.handler.complete();
166163
}
167164
}
@@ -176,7 +173,7 @@ public void completeWithError(Throwable ex) {
176173
synchronized (this) {
177174
this.complete = true;
178175
this.failure = ex;
179-
if (handler != null) {
176+
if (this.handler != null) {
180177
this.handler.completeWithError(ex);
181178
}
182179
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,17 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.web.servlet.mvc.method.annotation;
1718

1819
import java.io.IOException;
1920
import java.io.OutputStream;
20-
import java.lang.reflect.ParameterizedType;
21-
import java.lang.reflect.Type;
2221
import java.util.List;
23-
2422
import javax.servlet.http.HttpServletResponse;
2523

2624
import org.apache.commons.logging.Log;
2725
import org.apache.commons.logging.LogFactory;
26+
2827
import org.springframework.core.MethodParameter;
2928
import org.springframework.core.ResolvableType;
3029
import org.springframework.http.HttpHeaders;
@@ -41,7 +40,6 @@
4140
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
4241
import org.springframework.web.method.support.ModelAndViewContainer;
4342

44-
4543
/**
4644
* Supports return values of type {@link ResponseBodyEmitter} and also
4745
* {@code ResponseEntity<ResponseBodyEmitter>}.
@@ -61,6 +59,7 @@ public ResponseBodyEmitterReturnValueHandler(List<HttpMessageConverter<?>> messa
6159
this.messageConverters = messageConverters;
6260
}
6361

62+
6463
@Override
6564
public boolean supportsReturnType(MethodParameter returnType) {
6665
if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
@@ -121,13 +120,11 @@ private class HttpMessageConvertingHandler implements ResponseBodyEmitter.Handle
121120

122121
private final DeferredResult<?> deferredResult;
123122

124-
125123
public HttpMessageConvertingHandler(ServerHttpResponse outputMessage, DeferredResult<?> deferredResult) {
126124
this.outputMessage = outputMessage;
127125
this.deferredResult = deferredResult;
128126
}
129127

130-
131128
@Override
132129
public void send(Object data, MediaType mediaType) throws IOException {
133130
sendInternal(data, mediaType);
@@ -145,7 +142,7 @@ private <T> void sendInternal(T data, MediaType mediaType) throws IOException {
145142
return;
146143
}
147144
}
148-
throw new IllegalArgumentException("No suitable converter for " + data);
145+
throw new IllegalArgumentException("No suitable converter for " + data.getClass());
149146
}
150147

151148
@Override
@@ -170,13 +167,11 @@ private static class StreamingServletServerHttpResponse implements ServerHttpRes
170167

171168
private final HttpHeaders mutableHeaders = new HttpHeaders();
172169

173-
174170
public StreamingServletServerHttpResponse(ServerHttpResponse delegate) {
175171
this.delegate = delegate;
176172
this.mutableHeaders.putAll(delegate.getHeaders());
177173
}
178174

179-
180175
@Override
181176
public void setStatusCode(HttpStatus status) {
182177
this.delegate.setStatusCode(status);

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.web.servlet.mvc.method.annotation;
1718

1819
import java.io.IOException;
@@ -26,17 +27,15 @@
2627
import org.springframework.http.server.ServerHttpResponse;
2728

2829
/**
29-
* A specialization of
30-
* {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter
31-
* ResponseBodyEmitter} for sending
30+
* A specialization of {@link ResponseBodyEmitter} for sending
3231
* <a href="http://www.w3.org/TR/eventsource/">Server-Sent Events</a>.
3332
*
3433
* @author Rossen Stoyanchev
3534
* @since 4.2
3635
*/
3736
public class SseEmitter extends ResponseBodyEmitter {
3837

39-
public static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
38+
static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
4039

4140

4241
@Override
@@ -51,7 +50,6 @@ protected void extendResponse(ServerHttpResponse outputMessage) {
5150
/**
5251
* Send the object formatted as a single SSE "data" line. It's equivalent to:
5352
* <pre>
54-
*
5553
* // static import of SseEmitter.*
5654
*
5755
* SseEmitter emitter = new SseEmitter();
@@ -69,7 +67,6 @@ public void send(Object object) throws IOException {
6967
/**
7068
* Send the object formatted as a single SSE "data" line. It's equivalent to:
7169
* <pre>
72-
*
7370
* // static import of SseEmitter.*
7471
*
7572
* SseEmitter emitter = new SseEmitter();
@@ -91,7 +88,6 @@ public void send(Object object, MediaType mediaType) throws IOException {
9188
/**
9289
* Send an SSE event prepared with the given builder. For example:
9390
* <pre>
94-
*
9591
* // static import of SseEmitter
9692
*
9793
* SseEmitter emitter = new SseEmitter();
@@ -108,6 +104,7 @@ public void send(SseEventBuilder builder) throws IOException {
108104
}
109105
}
110106

107+
111108
public static SseEventBuilder event() {
112109
return new DefaultSseEventBuilder();
113110
}
@@ -156,6 +153,7 @@ public interface SseEventBuilder {
156153
Map<Object, MediaType> build();
157154
}
158155

156+
159157
/**
160158
* Default implementation of SseEventBuilder.
161159
*/
@@ -165,7 +163,6 @@ private static class DefaultSseEventBuilder implements SseEventBuilder {
165163

166164
private StringBuilder sb;
167165

168-
169166
@Override
170167
public SseEventBuilder comment(String comment) {
171168
append(":").append(comment != null ? comment : "").append("\n");

0 commit comments

Comments
 (0)