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