Skip to content

Commit a3636af

Browse files
committed
ResponseBodyEmitter allows complete after non-IOException
Closes gh-30687
1 parent 37d03e5 commit a3636af

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

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

+7-9
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public class ResponseBodyEmitter {
8787
* that may come for example from an application try-catch block on the
8888
* thread of the I/O error.
8989
*/
90-
private boolean sendFailed;
90+
private boolean ioErrorOnSend;
9191

9292
private final DefaultCallback timeoutCallback = new DefaultCallback();
9393

@@ -198,11 +198,10 @@ public synchronized void send(Object object, @Nullable MediaType mediaType) thro
198198
this.handler.send(object, mediaType);
199199
}
200200
catch (IOException ex) {
201-
this.sendFailed = true;
201+
this.ioErrorOnSend = true;
202202
throw ex;
203203
}
204204
catch (Throwable ex) {
205-
this.sendFailed = true;
206205
throw new IllegalStateException("Failed to send " + object, ex);
207206
}
208207
}
@@ -235,11 +234,10 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
235234
this.handler.send(items);
236235
}
237236
catch (IOException ex) {
238-
this.sendFailed = true;
237+
this.ioErrorOnSend = true;
239238
throw ex;
240239
}
241240
catch (Throwable ex) {
242-
this.sendFailed = true;
243241
throw new IllegalStateException("Failed to send " + items, ex);
244242
}
245243
}
@@ -257,8 +255,8 @@ private void sendInternal(Set<DataWithMediaType> items) throws IOException {
257255
* related events such as an error while {@link #send(Object) sending}.
258256
*/
259257
public synchronized void complete() {
260-
// Ignore, after send failure
261-
if (this.sendFailed) {
258+
// Ignore complete after IO failure on send
259+
if (this.ioErrorOnSend) {
262260
return;
263261
}
264262
this.complete = true;
@@ -279,8 +277,8 @@ public synchronized void complete() {
279277
* {@link #send(Object) sending}.
280278
*/
281279
public synchronized void completeWithError(Throwable ex) {
282-
// Ignore, after send failure
283-
if (this.sendFailed) {
280+
// Ignore complete after IO failure on send
281+
if (this.ioErrorOnSend) {
284282
return;
285283
}
286284
this.complete = true;

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

+39-7
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,9 @@ void sendBeforeHandlerInitializedWithError() throws Exception {
9292
}
9393

9494
@Test
95-
void sendFailsAfterComplete() throws Exception {
95+
void sendFailsAfterComplete() {
9696
this.emitter.complete();
97-
assertThatIllegalStateException().isThrownBy(() ->
98-
this.emitter.send("foo"));
97+
assertThatIllegalStateException().isThrownBy(() -> this.emitter.send("foo"));
9998
}
10099

101100
@Test
@@ -143,14 +142,47 @@ void sendWithError() throws Exception {
143142
verify(this.handler).onCompletion(any());
144143
verifyNoMoreInteractions(this.handler);
145144

146-
IOException failure = new IOException();
147-
willThrow(failure).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
148-
assertThatIOException().isThrownBy(() ->
149-
this.emitter.send("foo", MediaType.TEXT_PLAIN));
145+
willThrow(new IOException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
146+
assertThatIOException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
150147
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
151148
verifyNoMoreInteractions(this.handler);
152149
}
153150

151+
@Test // gh-30687
152+
void completeIgnoredAfterIOException() throws Exception {
153+
this.emitter.initialize(this.handler);
154+
verify(this.handler).onTimeout(any());
155+
verify(this.handler).onError(any());
156+
verify(this.handler).onCompletion(any());
157+
verifyNoMoreInteractions(this.handler);
158+
159+
willThrow(new IOException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
160+
assertThatIOException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
161+
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
162+
verifyNoMoreInteractions(this.handler);
163+
164+
this.emitter.complete();
165+
verifyNoMoreInteractions(this.handler);
166+
}
167+
168+
@Test // gh-30687
169+
void completeAfterNonIOException() throws Exception {
170+
this.emitter.initialize(this.handler);
171+
verify(this.handler).onTimeout(any());
172+
verify(this.handler).onError(any());
173+
verify(this.handler).onCompletion(any());
174+
verifyNoMoreInteractions(this.handler);
175+
176+
willThrow(new IllegalStateException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
177+
assertThatIllegalStateException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
178+
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
179+
verifyNoMoreInteractions(this.handler);
180+
181+
this.emitter.complete();
182+
verify(this.handler).complete();
183+
verifyNoMoreInteractions(this.handler);
184+
}
185+
154186
@Test
155187
void onTimeoutBeforeHandlerInitialized() throws Exception {
156188
Runnable runnable = mock();

0 commit comments

Comments
 (0)