2121import java .util .LinkedHashSet ;
2222import java .util .List ;
2323import java .util .Set ;
24+ import java .util .concurrent .atomic .AtomicBoolean ;
2425import java .util .function .Consumer ;
2526
2627import org .springframework .http .MediaType ;
@@ -76,7 +77,7 @@ public class ResponseBodyEmitter {
7677 private final Set <DataWithMediaType > earlySendAttempts = new LinkedHashSet <>(8 );
7778
7879 /** Store successful completion before the handler is initialized. */
79- private boolean complete ;
80+ private final AtomicBoolean complete = new AtomicBoolean () ;
8081
8182 /** Store an error before the handler is initialized. */
8283 @ Nullable
@@ -127,7 +128,7 @@ synchronized void initialize(Handler handler) throws IOException {
127128 this .earlySendAttempts .clear ();
128129 }
129130
130- if (this .complete ) {
131+ if (this .complete . get () ) {
131132 if (this .failure != null ) {
132133 this .handler .completeWithError (this .failure );
133134 }
@@ -142,11 +143,12 @@ synchronized void initialize(Handler handler) throws IOException {
142143 }
143144 }
144145
145- synchronized void initializeWithError (Throwable ex ) {
146- this .complete = true ;
147- this .failure = ex ;
148- this .earlySendAttempts .clear ();
149- this .errorCallback .accept (ex );
146+ void initializeWithError (Throwable ex ) {
147+ if (this .complete .compareAndSet (false , true )) {
148+ this .failure = ex ;
149+ this .earlySendAttempts .clear ();
150+ this .errorCallback .accept (ex );
151+ }
150152 }
151153
152154 /**
@@ -184,7 +186,7 @@ public void send(Object object) throws IOException {
184186 * @throws java.lang.IllegalStateException wraps any other errors
185187 */
186188 public synchronized void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
187- Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
189+ Assert .state (!this .complete . get () , () -> "ResponseBodyEmitter has already completed" +
188190 (this .failure != null ? " with error: " + this .failure : "" ));
189191 if (this .handler != null ) {
190192 try {
@@ -212,7 +214,7 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
212214 * @since 6.0.12
213215 */
214216 public synchronized void send (Set <DataWithMediaType > items ) throws IOException {
215- Assert .state (!this .complete , () -> "ResponseBodyEmitter has already completed" +
217+ Assert .state (!this .complete . get () , () -> "ResponseBodyEmitter has already completed" +
216218 (this .failure != null ? " with error: " + this .failure : "" ));
217219 sendInternal (items );
218220 }
@@ -245,9 +247,8 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
245247 * to complete request processing. It should not be used after container
246248 * related events such as an error while {@link #send(Object) sending}.
247249 */
248- public synchronized void complete () {
249- this .complete = true ;
250- if (this .handler != null ) {
250+ public void complete () {
251+ if (this .complete .compareAndSet (false , true ) && this .handler != null ) {
251252 this .handler .complete ();
252253 }
253254 }
@@ -263,11 +264,12 @@ public synchronized void complete() {
263264 * container related events such as an error while
264265 * {@link #send(Object) sending}.
265266 */
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 );
267+ public void completeWithError (Throwable ex ) {
268+ if (this .complete .compareAndSet (false , true )) {
269+ this .failure = ex ;
270+ if (this .handler != null ) {
271+ this .handler .completeWithError (ex );
272+ }
271273 }
272274 }
273275
@@ -276,7 +278,7 @@ public synchronized void completeWithError(Throwable ex) {
276278 * called from a container thread when an async request times out.
277279 * <p>As of 6.2, one can register multiple callbacks for this event.
278280 */
279- public synchronized void onTimeout (Runnable callback ) {
281+ public void onTimeout (Runnable callback ) {
280282 this .timeoutCallback .addDelegate (callback );
281283 }
282284
@@ -287,7 +289,7 @@ public synchronized void onTimeout(Runnable callback) {
287289 * <p>As of 6.2, one can register multiple callbacks for this event.
288290 * @since 5.0
289291 */
290- public synchronized void onError (Consumer <Throwable > callback ) {
292+ public void onError (Consumer <Throwable > callback ) {
291293 this .errorCallback .addDelegate (callback );
292294 }
293295
@@ -298,7 +300,7 @@ public synchronized void onError(Consumer<Throwable> callback) {
298300 * detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
299301 * <p>As of 6.2, one can register multiple callbacks for this event.
300302 */
301- public synchronized void onCompletion (Runnable callback ) {
303+ public void onCompletion (Runnable callback ) {
302304 this .completionCallback .addDelegate (callback );
303305 }
304306
@@ -369,15 +371,15 @@ public MediaType getMediaType() {
369371
370372 private class DefaultCallback implements Runnable {
371373
372- private List <Runnable > delegates = new ArrayList <>(1 );
374+ private final List <Runnable > delegates = new ArrayList <>(1 );
373375
374- public void addDelegate (Runnable delegate ) {
376+ public synchronized void addDelegate (Runnable delegate ) {
375377 this .delegates .add (delegate );
376378 }
377379
378380 @ Override
379381 public void run () {
380- ResponseBodyEmitter .this .complete = true ;
382+ ResponseBodyEmitter .this .complete . compareAndSet ( false , true ) ;
381383 for (Runnable delegate : this .delegates ) {
382384 delegate .run ();
383385 }
@@ -387,15 +389,15 @@ public void run() {
387389
388390 private class ErrorCallback implements Consumer <Throwable > {
389391
390- private List <Consumer <Throwable >> delegates = new ArrayList <>(1 );
392+ private final List <Consumer <Throwable >> delegates = new ArrayList <>(1 );
391393
392- public void addDelegate (Consumer <Throwable > callback ) {
394+ public synchronized void addDelegate (Consumer <Throwable > callback ) {
393395 this .delegates .add (callback );
394396 }
395397
396398 @ Override
397399 public void accept (Throwable t ) {
398- ResponseBodyEmitter .this .complete = true ;
400+ ResponseBodyEmitter .this .complete . compareAndSet ( false , true ) ;
399401 for (Consumer <Throwable > delegate : this .delegates ) {
400402 delegate .accept (t );
401403 }
0 commit comments