21
21
import java .util .LinkedHashSet ;
22
22
import java .util .List ;
23
23
import java .util .Set ;
24
+ import java .util .concurrent .locks .Lock ;
25
+ import java .util .concurrent .locks .ReentrantLock ;
24
26
import java .util .function .Consumer ;
25
27
26
28
import org .springframework .http .MediaType ;
62
64
* @author Rossen Stoyanchev
63
65
* @author Juergen Hoeller
64
66
* @author Brian Clozel
67
+ * @author Taeik Lim
65
68
* @since 4.2
66
69
*/
67
70
public class ResponseBodyEmitter {
@@ -88,6 +91,8 @@ public class ResponseBodyEmitter {
88
91
89
92
private final DefaultCallback completionCallback = new DefaultCallback ();
90
93
94
+ /** Guards access to write operations on the response. */
95
+ protected final Lock writeLock = new ReentrantLock ();
91
96
92
97
/**
93
98
* Create a new ResponseBodyEmitter instance.
@@ -117,36 +122,48 @@ public Long getTimeout() {
117
122
}
118
123
119
124
120
- synchronized void initialize (Handler handler ) throws IOException {
121
- this .handler = handler ;
122
-
125
+ void initialize (Handler handler ) throws IOException {
126
+ this .writeLock .lock ();
123
127
try {
124
- sendInternal (this .earlySendAttempts );
125
- }
126
- finally {
127
- this .earlySendAttempts .clear ();
128
- }
128
+ this .handler = handler ;
129
+
130
+ try {
131
+ sendInternal (this .earlySendAttempts );
132
+ }
133
+ finally {
134
+ this .earlySendAttempts .clear ();
135
+ }
129
136
130
- if (this .complete ) {
131
- if (this .failure != null ) {
132
- this .handler .completeWithError (this .failure );
137
+ if (this .complete ) {
138
+ if (this .failure != null ) {
139
+ this .handler .completeWithError (this .failure );
140
+ }
141
+ else {
142
+ this .handler .complete ();
143
+ }
133
144
}
134
145
else {
135
- this .handler .complete ();
146
+ this .handler .onTimeout (this .timeoutCallback );
147
+ this .handler .onError (this .errorCallback );
148
+ this .handler .onCompletion (this .completionCallback );
136
149
}
137
150
}
138
- else {
139
- this .handler .onTimeout (this .timeoutCallback );
140
- this .handler .onError (this .errorCallback );
141
- this .handler .onCompletion (this .completionCallback );
151
+ finally {
152
+ this .writeLock .unlock ();
142
153
}
143
154
}
144
155
145
- synchronized void initializeWithError (Throwable ex ) {
146
- this .complete = true ;
147
- this .failure = ex ;
148
- this .earlySendAttempts .clear ();
149
- this .errorCallback .accept (ex );
156
+ void initializeWithError (Throwable ex ) {
157
+ this .writeLock .lock ();
158
+ try {
159
+ this .complete = true ;
160
+ this .failure = ex ;
161
+ this .earlySendAttempts .clear ();
162
+ this .errorCallback .accept (ex );
163
+ }
164
+ finally {
165
+ this .writeLock .unlock ();
166
+ }
150
167
}
151
168
152
169
/**
@@ -183,22 +200,28 @@ public void send(Object object) throws IOException {
183
200
* @throws IOException raised when an I/O error occurs
184
201
* @throws java.lang.IllegalStateException wraps any other errors
185
202
*/
186
- public synchronized void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
203
+ public void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
187
204
Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
188
205
(this .failure != null ? " with error: " + this .failure : "" ));
189
- if (this .handler != null ) {
190
- try {
191
- this .handler .send (object , mediaType );
192
- }
193
- catch (IOException ex ) {
194
- throw ex ;
206
+ this .writeLock .lock ();
207
+ try {
208
+ if (this .handler != null ) {
209
+ try {
210
+ this .handler .send (object , mediaType );
211
+ }
212
+ catch (IOException ex ) {
213
+ throw ex ;
214
+ }
215
+ catch (Throwable ex ) {
216
+ throw new IllegalStateException ("Failed to send " + object , ex );
217
+ }
195
218
}
196
- catch ( Throwable ex ) {
197
- throw new IllegalStateException ( "Failed to send " + object , ex );
219
+ else {
220
+ this . earlySendAttempts . add ( new DataWithMediaType ( object , mediaType ) );
198
221
}
199
222
}
200
- else {
201
- this .earlySendAttempts . add ( new DataWithMediaType ( object , mediaType ) );
223
+ finally {
224
+ this .writeLock . unlock ( );
202
225
}
203
226
}
204
227
@@ -211,10 +234,16 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
211
234
* @throws java.lang.IllegalStateException wraps any other errors
212
235
* @since 6.0.12
213
236
*/
214
- public synchronized void send (Set <DataWithMediaType > items ) throws IOException {
237
+ public void send (Set <DataWithMediaType > items ) throws IOException {
215
238
Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
216
239
(this .failure != null ? " with error: " + this .failure : "" ));
217
- sendInternal (items );
240
+ this .writeLock .lock ();
241
+ try {
242
+ sendInternal (items );
243
+ }
244
+ finally {
245
+ this .writeLock .unlock ();
246
+ }
218
247
}
219
248
220
249
private void sendInternal (Set <DataWithMediaType > items ) throws IOException {
@@ -245,10 +274,16 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
245
274
* to complete request processing. It should not be used after container
246
275
* related events such as an error while {@link #send(Object) sending}.
247
276
*/
248
- public synchronized void complete () {
249
- this .complete = true ;
250
- if (this .handler != null ) {
251
- this .handler .complete ();
277
+ public void complete () {
278
+ this .writeLock .lock ();
279
+ try {
280
+ this .complete = true ;
281
+ if (this .handler != null ) {
282
+ this .handler .complete ();
283
+ }
284
+ }
285
+ finally {
286
+ this .writeLock .unlock ();
252
287
}
253
288
}
254
289
@@ -263,11 +298,17 @@ public synchronized void complete() {
263
298
* container related events such as an error while
264
299
* {@link #send(Object) sending}.
265
300
*/
266
- public synchronized void completeWithError (Throwable ex ) {
267
- this .complete = true ;
268
- this .failure = ex ;
269
- if (this .handler != null ) {
270
- this .handler .completeWithError (ex );
301
+ public void completeWithError (Throwable ex ) {
302
+ this .writeLock .lock ();
303
+ try {
304
+ this .complete = true ;
305
+ this .failure = ex ;
306
+ if (this .handler != null ) {
307
+ this .handler .completeWithError (ex );
308
+ }
309
+ }
310
+ finally {
311
+ this .writeLock .unlock ();
271
312
}
272
313
}
273
314
@@ -276,8 +317,14 @@ public synchronized void completeWithError(Throwable ex) {
276
317
* called from a container thread when an async request times out.
277
318
* <p>As of 6.2, one can register multiple callbacks for this event.
278
319
*/
279
- public synchronized void onTimeout (Runnable callback ) {
280
- this .timeoutCallback .addDelegate (callback );
320
+ public void onTimeout (Runnable callback ) {
321
+ this .writeLock .lock ();
322
+ try {
323
+ this .timeoutCallback .addDelegate (callback );
324
+ }
325
+ finally {
326
+ this .writeLock .unlock ();
327
+ }
281
328
}
282
329
283
330
/**
@@ -287,8 +334,14 @@ public synchronized void onTimeout(Runnable callback) {
287
334
* <p>As of 6.2, one can register multiple callbacks for this event.
288
335
* @since 5.0
289
336
*/
290
- public synchronized void onError (Consumer <Throwable > callback ) {
291
- this .errorCallback .addDelegate (callback );
337
+ public void onError (Consumer <Throwable > callback ) {
338
+ this .writeLock .lock ();
339
+ try {
340
+ this .errorCallback .addDelegate (callback );
341
+ }
342
+ finally {
343
+ this .writeLock .unlock ();
344
+ }
292
345
}
293
346
294
347
/**
@@ -298,8 +351,14 @@ public synchronized void onError(Consumer<Throwable> callback) {
298
351
* detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
299
352
* <p>As of 6.2, one can register multiple callbacks for this event.
300
353
*/
301
- public synchronized void onCompletion (Runnable callback ) {
302
- this .completionCallback .addDelegate (callback );
354
+ public void onCompletion (Runnable callback ) {
355
+ this .writeLock .lock ();
356
+ try {
357
+ this .completionCallback .addDelegate (callback );
358
+ }
359
+ finally {
360
+ this .writeLock .unlock ();
361
+ }
303
362
}
304
363
305
364
0 commit comments