2121import java .util .LinkedHashSet ;
2222import java .util .List ;
2323import java .util .Set ;
24+ import java .util .concurrent .locks .Lock ;
25+ import java .util .concurrent .locks .ReentrantLock ;
2426import java .util .function .Consumer ;
2527
2628import org .springframework .http .MediaType ;
6264 * @author Rossen Stoyanchev
6365 * @author Juergen Hoeller
6466 * @author Brian Clozel
67+ * @author Taeik Lim
6568 * @since 4.2
6669 */
6770public class ResponseBodyEmitter {
@@ -88,6 +91,8 @@ public class ResponseBodyEmitter {
8891
8992 private final DefaultCallback completionCallback = new DefaultCallback ();
9093
94+ /** Guards access to write operations on the response. */
95+ protected final Lock writeLock = new ReentrantLock ();
9196
9297 /**
9398 * Create a new ResponseBodyEmitter instance.
@@ -117,36 +122,48 @@ public Long getTimeout() {
117122 }
118123
119124
120- synchronized void initialize (Handler handler ) throws IOException {
121- this .handler = handler ;
122-
125+ void initialize (Handler handler ) throws IOException {
126+ this .writeLock .lock ();
123127 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+ }
129136
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+ }
133144 }
134145 else {
135- this .handler .complete ();
146+ this .handler .onTimeout (this .timeoutCallback );
147+ this .handler .onError (this .errorCallback );
148+ this .handler .onCompletion (this .completionCallback );
136149 }
137150 }
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 ();
142153 }
143154 }
144155
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+ }
150167 }
151168
152169 /**
@@ -183,22 +200,28 @@ public void send(Object object) throws IOException {
183200 * @throws IOException raised when an I/O error occurs
184201 * @throws java.lang.IllegalStateException wraps any other errors
185202 */
186- public synchronized void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
203+ public void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
187204 Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
188205 (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+ }
195218 }
196- catch ( Throwable ex ) {
197- throw new IllegalStateException ( "Failed to send " + object , ex );
219+ else {
220+ this . earlySendAttempts . add ( new DataWithMediaType ( object , mediaType ) );
198221 }
199222 }
200- else {
201- this .earlySendAttempts . add ( new DataWithMediaType ( object , mediaType ) );
223+ finally {
224+ this .writeLock . unlock ( );
202225 }
203226 }
204227
@@ -211,10 +234,16 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
211234 * @throws java.lang.IllegalStateException wraps any other errors
212235 * @since 6.0.12
213236 */
214- public synchronized void send (Set <DataWithMediaType > items ) throws IOException {
237+ public void send (Set <DataWithMediaType > items ) throws IOException {
215238 Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
216239 (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+ }
218247 }
219248
220249 private void sendInternal (Set <DataWithMediaType > items ) throws IOException {
@@ -245,10 +274,16 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
245274 * to complete request processing. It should not be used after container
246275 * related events such as an error while {@link #send(Object) sending}.
247276 */
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 ();
252287 }
253288 }
254289
@@ -263,11 +298,17 @@ public synchronized void complete() {
263298 * container related events such as an error while
264299 * {@link #send(Object) sending}.
265300 */
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 ();
271312 }
272313 }
273314
@@ -276,8 +317,14 @@ public synchronized void completeWithError(Throwable ex) {
276317 * called from a container thread when an async request times out.
277318 * <p>As of 6.2, one can register multiple callbacks for this event.
278319 */
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+ }
281328 }
282329
283330 /**
@@ -287,8 +334,14 @@ public synchronized void onTimeout(Runnable callback) {
287334 * <p>As of 6.2, one can register multiple callbacks for this event.
288335 * @since 5.0
289336 */
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+ }
292345 }
293346
294347 /**
@@ -298,8 +351,14 @@ public synchronized void onError(Consumer<Throwable> callback) {
298351 * detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
299352 * <p>As of 6.2, one can register multiple callbacks for this event.
300353 */
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+ }
303362 }
304363
305364
0 commit comments