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