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 ;
27+ import  java .util .logging .Handler ;
2528
2629import  org .jspecify .annotations .Nullable ;
2730
6366 * @author Rossen Stoyanchev 
6467 * @author Juergen Hoeller 
6568 * @author Brian Clozel 
69+  * @author Taeik Lim 
6670 * @since 4.2 
6771 */ 
6872public  class  ResponseBodyEmitter  {
@@ -86,6 +90,8 @@ public class ResponseBodyEmitter {
8690
8791	private  final  DefaultCallback  completionCallback  = new  DefaultCallback ();
8892
93+ 	/** Guards access to write operations on the response. */ 
94+ 	protected  final  Lock  writeLock  = new  ReentrantLock ();
8995
9096	/** 
9197	 * Create a new ResponseBodyEmitter instance. 
@@ -114,36 +120,46 @@ public ResponseBodyEmitter(Long timeout) {
114120	}
115121
116122
117- 	synchronized  void  initialize (Handler  handler ) throws  IOException  {
118- 		this .handler  = handler ;
119- 
123+ 	void  initialize (Handler  handler ) throws  IOException  {
124+ 		this .writeLock .lock ();
120125		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+ 			}
126134
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+ 				}
130142			}
131143			else  {
132- 				this .handler .complete ();
144+ 				this .handler .onTimeout (this .timeoutCallback );
145+ 				this .handler .onError (this .errorCallback );
146+ 				this .handler .onCompletion (this .completionCallback );
133147			}
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 ();
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+ 		} finally  {
161+ 			this .writeLock .unlock ();
162+ 		}
147163	}
148164
149165	/** 
@@ -180,22 +196,27 @@ public void send(Object object) throws IOException {
180196	 * @throws IOException raised when an I/O error occurs 
181197	 * @throws java.lang.IllegalStateException wraps any other errors 
182198	 */ 
183- 	public  synchronized   void  send (Object  object , @ Nullable  MediaType  mediaType ) throws  IOException  {
199+ 	public  void  send (Object  object , @ Nullable  MediaType  mediaType ) throws  IOException  {
184200		Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed"  +
185201				(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+ 				}
192214			}
193- 			catch  ( Throwable   ex )  {
194- 				throw   new  IllegalStateException ( "Failed to send "  +  object , ex );
215+ 			else  {
216+ 				this . earlySendAttempts . add ( new  DataWithMediaType ( object , mediaType ) );
195217			}
196- 		}
197- 		else  {
198- 			this .earlySendAttempts .add (new  DataWithMediaType (object , mediaType ));
218+ 		} finally  {
219+ 			this .writeLock .unlock ();
199220		}
200221	}
201222
@@ -208,10 +229,15 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
208229	 * @throws java.lang.IllegalStateException wraps any other errors 
209230	 * @since 6.0.12 
210231	 */ 
211- 	public  synchronized   void  send (Set <DataWithMediaType > items ) throws  IOException  {
232+ 	public  void  send (Set <DataWithMediaType > items ) throws  IOException  {
212233		Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed"  +
213234				(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+ 		}
215241	}
216242
217243	private  void  sendInternal (Set <DataWithMediaType > items ) throws  IOException  {
@@ -242,10 +268,15 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
242268	 * to complete request processing. It should not be used after container 
243269	 * related events such as an error while {@link #send(Object) sending}. 
244270	 */ 
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 ();
249280		}
250281	}
251282
@@ -260,11 +291,16 @@ public synchronized void complete() {
260291	 * container related events such as an error while 
261292	 * {@link #send(Object) sending}. 
262293	 */ 
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 ();
268304		}
269305	}
270306
@@ -273,8 +309,13 @@ public synchronized void completeWithError(Throwable ex) {
273309	 * called from a container thread when an async request times out. 
274310	 * <p>As of 6.2, one can register multiple callbacks for this event. 
275311	 */ 
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+ 		}
278319	}
279320
280321	/** 
@@ -284,8 +325,13 @@ public synchronized void onTimeout(Runnable callback) {
284325	 * <p>As of 6.2, one can register multiple callbacks for this event. 
285326	 * @since 5.0 
286327	 */ 
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+ 		}
289335	}
290336
291337	/** 
@@ -295,8 +341,13 @@ public synchronized void onError(Consumer<Throwable> callback) {
295341	 * detecting that a {@code ResponseBodyEmitter} instance is no longer usable. 
296342	 * <p>As of 6.2, one can register multiple callbacks for this event. 
297343	 */ 
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+ 		}
300351	}
301352
302353
0 commit comments