Skip to content

Commit e67f892

Browse files
lucky8987simonbasle
authored andcommitted
Use optimistic locking where possible in ResponseBodyEmitter
Closes gh-33831
1 parent 1ced8c3 commit e67f892

File tree

1 file changed

+28
-26
lines changed

1 file changed

+28
-26
lines changed

Diff for: spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java

+28-26
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.LinkedHashSet;
2222
import java.util.List;
2323
import java.util.Set;
24+
import java.util.concurrent.atomic.AtomicBoolean;
2425
import java.util.function.Consumer;
2526

2627
import 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

Comments
 (0)