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 .jspecify .annotations .Nullable ;
6365 * @author Rossen Stoyanchev 
6466 * @author Juergen Hoeller 
6567 * @author Brian Clozel 
68+  * @author Taeik Lim 
6669 * @since 4.2 
6770 */ 
6871public  class  ResponseBodyEmitter  {
@@ -86,6 +89,8 @@ public class ResponseBodyEmitter {
8689
8790	private  final  DefaultCallback  completionCallback  = new  DefaultCallback ();
8891
92+ 	/** Guards access to write operations on the response. */ 
93+ 	protected  final  Lock  writeLock  = new  ReentrantLock ();
8994
9095	/** 
9196	 * Create a new ResponseBodyEmitter instance. 
@@ -114,36 +119,48 @@ public ResponseBodyEmitter(Long timeout) {
114119	}
115120
116121
117- 	synchronized  void  initialize (Handler  handler ) throws  IOException  {
118- 		this .handler  = handler ;
119- 
122+ 	void  initialize (Handler  handler ) throws  IOException  {
123+ 		this .writeLock .lock ();
120124		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+ 			}
126133
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+ 				}
130141			}
131142			else  {
132- 				this .handler .complete ();
143+ 				this .handler .onTimeout (this .timeoutCallback );
144+ 				this .handler .onError (this .errorCallback );
145+ 				this .handler .onCompletion (this .completionCallback );
133146			}
134147		}
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 ();
139150		}
140151	}
141152
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+ 		}
147164	}
148165
149166	/** 
@@ -180,22 +197,28 @@ public void send(Object object) throws IOException {
180197	 * @throws IOException raised when an I/O error occurs 
181198	 * @throws java.lang.IllegalStateException wraps any other errors 
182199	 */ 
183- 	public  synchronized   void  send (Object  object , @ Nullable  MediaType  mediaType ) throws  IOException  {
200+ 	public  void  send (Object  object , @ Nullable  MediaType  mediaType ) throws  IOException  {
184201		Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed"  +
185202				(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+ 				}
192215			}
193- 			catch  ( Throwable   ex )  {
194- 				throw   new  IllegalStateException ( "Failed to send "  +  object , ex );
216+ 			else  {
217+ 				this . earlySendAttempts . add ( new  DataWithMediaType ( object , mediaType ) );
195218			}
196219		}
197- 		else  {
198- 			this .earlySendAttempts . add ( new   DataWithMediaType ( object ,  mediaType ) );
220+ 		finally  {
221+ 			this .writeLock . unlock ( );
199222		}
200223	}
201224
@@ -208,10 +231,16 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
208231	 * @throws java.lang.IllegalStateException wraps any other errors 
209232	 * @since 6.0.12 
210233	 */ 
211- 	public  synchronized   void  send (Set <DataWithMediaType > items ) throws  IOException  {
234+ 	public  void  send (Set <DataWithMediaType > items ) throws  IOException  {
212235		Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed"  +
213236				(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+ 		}
215244	}
216245
217246	private  void  sendInternal (Set <DataWithMediaType > items ) throws  IOException  {
@@ -242,10 +271,16 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
242271	 * to complete request processing. It should not be used after container 
243272	 * related events such as an error while {@link #send(Object) sending}. 
244273	 */ 
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 ();
249284		}
250285	}
251286
@@ -260,11 +295,17 @@ public synchronized void complete() {
260295	 * container related events such as an error while 
261296	 * {@link #send(Object) sending}. 
262297	 */ 
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 ();
268309		}
269310	}
270311
@@ -273,8 +314,14 @@ public synchronized void completeWithError(Throwable ex) {
273314	 * called from a container thread when an async request times out. 
274315	 * <p>As of 6.2, one can register multiple callbacks for this event. 
275316	 */ 
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+ 		}
278325	}
279326
280327	/** 
@@ -284,8 +331,14 @@ public synchronized void onTimeout(Runnable callback) {
284331	 * <p>As of 6.2, one can register multiple callbacks for this event. 
285332	 * @since 5.0 
286333	 */ 
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+ 		}
289342	}
290343
291344	/** 
@@ -295,8 +348,14 @@ public synchronized void onError(Consumer<Throwable> callback) {
295348	 * detecting that a {@code ResponseBodyEmitter} instance is no longer usable. 
296349	 * <p>As of 6.2, one can register multiple callbacks for this event. 
297350	 */ 
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+ 		}
300359	}
301360
302361
0 commit comments