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