1
1
/*
2
- * Copyright 2002-2025 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
21
21
import java .util .LinkedHashSet ;
22
22
import java .util .List ;
23
23
import java .util .Set ;
24
- import java .util .concurrent .atomic .AtomicReference ;
24
+ import java .util .concurrent .atomic .AtomicBoolean ;
25
25
import java .util .function .Consumer ;
26
26
27
27
import org .springframework .http .MediaType ;
@@ -73,20 +73,21 @@ public class ResponseBodyEmitter {
73
73
@ Nullable
74
74
private Handler handler ;
75
75
76
- private final AtomicReference <State > state = new AtomicReference <>(State .START );
77
-
78
76
/** Store send data before handler is initialized. */
79
77
private final Set <DataWithMediaType > earlySendAttempts = new LinkedHashSet <>(8 );
80
78
79
+ /** Store successful completion before the handler is initialized. */
80
+ private final AtomicBoolean complete = new AtomicBoolean ();
81
+
81
82
/** Store an error before the handler is initialized. */
82
83
@ Nullable
83
84
private Throwable failure ;
84
85
85
- private final TimeoutCallback timeoutCallback = new TimeoutCallback ();
86
+ private final DefaultCallback timeoutCallback = new DefaultCallback ();
86
87
87
88
private final ErrorCallback errorCallback = new ErrorCallback ();
88
89
89
- private final CompletionCallback completionCallback = new CompletionCallback ();
90
+ private final DefaultCallback completionCallback = new DefaultCallback ();
90
91
91
92
92
93
/**
@@ -127,7 +128,7 @@ synchronized void initialize(Handler handler) throws IOException {
127
128
this .earlySendAttempts .clear ();
128
129
}
129
130
130
- if (this .state .get () == State . COMPLETE ) {
131
+ if (this .complete .get ()) {
131
132
if (this .failure != null ) {
132
133
this .handler .completeWithError (this .failure );
133
134
}
@@ -143,7 +144,7 @@ synchronized void initialize(Handler handler) throws IOException {
143
144
}
144
145
145
146
void initializeWithError (Throwable ex ) {
146
- if (this .state .compareAndSet (State . START , State . COMPLETE )) {
147
+ if (this .complete .compareAndSet (false , true )) {
147
148
this .failure = ex ;
148
149
this .earlySendAttempts .clear ();
149
150
this .errorCallback .accept (ex );
@@ -185,7 +186,8 @@ public void send(Object object) throws IOException {
185
186
* @throws java.lang.IllegalStateException wraps any other errors
186
187
*/
187
188
public synchronized void send (Object object , @ Nullable MediaType mediaType ) throws IOException {
188
- assertNotComplete ();
189
+ Assert .state (!this .complete .get (), () -> "ResponseBodyEmitter has already completed" +
190
+ (this .failure != null ? " with error: " + this .failure : "" ));
189
191
if (this .handler != null ) {
190
192
try {
191
193
this .handler .send (object , mediaType );
@@ -212,13 +214,9 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
212
214
* @since 6.0.12
213
215
*/
214
216
public synchronized void send (Set <DataWithMediaType > items ) throws IOException {
215
- assertNotComplete ();
216
- sendInternal (items );
217
- }
218
-
219
- private void assertNotComplete () {
220
- Assert .state (this .state .get () == State .START , () -> "ResponseBodyEmitter has already completed" +
217
+ Assert .state (!this .complete .get (), () -> "ResponseBodyEmitter has already completed" +
221
218
(this .failure != null ? " with error: " + this .failure : "" ));
219
+ sendInternal (items );
222
220
}
223
221
224
222
private void sendInternal (Set <DataWithMediaType > items ) throws IOException {
@@ -250,7 +248,7 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
250
248
* related events such as an error while {@link #send(Object) sending}.
251
249
*/
252
250
public void complete () {
253
- if (trySetComplete ( ) && this .handler != null ) {
251
+ if (this . complete . compareAndSet ( false , true ) && this .handler != null ) {
254
252
this .handler .complete ();
255
253
}
256
254
}
@@ -267,19 +265,14 @@ public void complete() {
267
265
* {@link #send(Object) sending}.
268
266
*/
269
267
public void completeWithError (Throwable ex ) {
270
- if (trySetComplete ( )) {
268
+ if (this . complete . compareAndSet ( false , true )) {
271
269
this .failure = ex ;
272
270
if (this .handler != null ) {
273
271
this .handler .completeWithError (ex );
274
272
}
275
273
}
276
274
}
277
275
278
- private boolean trySetComplete () {
279
- return (this .state .compareAndSet (State .START , State .COMPLETE ) ||
280
- (this .state .compareAndSet (State .TIMEOUT , State .COMPLETE )));
281
- }
282
-
283
276
/**
284
277
* Register code to invoke when the async request times out. This method is
285
278
* called from a container thread when an async request times out.
@@ -376,7 +369,7 @@ public MediaType getMediaType() {
376
369
}
377
370
378
371
379
- private class TimeoutCallback implements Runnable {
372
+ private class DefaultCallback implements Runnable {
380
373
381
374
private final List <Runnable > delegates = new ArrayList <>(1 );
382
375
@@ -386,10 +379,9 @@ public synchronized void addDelegate(Runnable delegate) {
386
379
387
380
@ Override
388
381
public void run () {
389
- if (ResponseBodyEmitter .this .state .compareAndSet (State .START , State .TIMEOUT )) {
390
- for (Runnable delegate : this .delegates ) {
391
- delegate .run ();
392
- }
382
+ ResponseBodyEmitter .this .complete .compareAndSet (false , true );
383
+ for (Runnable delegate : this .delegates ) {
384
+ delegate .run ();
393
385
}
394
386
}
395
387
}
@@ -405,51 +397,11 @@ public synchronized void addDelegate(Consumer<Throwable> callback) {
405
397
406
398
@ Override
407
399
public void accept (Throwable t ) {
408
- if (ResponseBodyEmitter .this .state .compareAndSet (State .START , State .COMPLETE )) {
409
- for (Consumer <Throwable > delegate : this .delegates ) {
410
- delegate .accept (t );
411
- }
412
- }
413
- }
414
- }
415
-
416
-
417
- private class CompletionCallback implements Runnable {
418
-
419
- private final List <Runnable > delegates = new ArrayList <>(1 );
420
-
421
- public synchronized void addDelegate (Runnable delegate ) {
422
- this .delegates .add (delegate );
423
- }
424
-
425
- @ Override
426
- public void run () {
427
- if (ResponseBodyEmitter .this .state .compareAndSet (State .START , State .COMPLETE )) {
428
- for (Runnable delegate : this .delegates ) {
429
- delegate .run ();
430
- }
400
+ ResponseBodyEmitter .this .complete .compareAndSet (false , true );
401
+ for (Consumer <Throwable > delegate : this .delegates ) {
402
+ delegate .accept (t );
431
403
}
432
404
}
433
405
}
434
406
435
-
436
- /**
437
- * Represents a state for {@link ResponseBodyEmitter}.
438
- * <p><pre>
439
- * START ----+
440
- * | |
441
- * v |
442
- * TIMEOUT |
443
- * | |
444
- * v |
445
- * COMPLETE <--+
446
- * </pre>
447
- * @since 6.2.4
448
- */
449
- private enum State {
450
- START ,
451
- TIMEOUT , // handling a timeout
452
- COMPLETE
453
- }
454
-
455
407
}
0 commit comments