Browse Source

ResponseBodyEmitter allows complete after non-IOException

Closes gh-30687
pull/29430/head
rstoyanchev 1 year ago
parent
commit
a3636affa2
  1. 16
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java
  2. 46
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterTests.java

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

@ -87,7 +87,7 @@ public class ResponseBodyEmitter { @@ -87,7 +87,7 @@ public class ResponseBodyEmitter {
* that may come for example from an application try-catch block on the
* thread of the I/O error.
*/
private boolean sendFailed;
private boolean ioErrorOnSend;
private final DefaultCallback timeoutCallback = new DefaultCallback();
@ -198,11 +198,10 @@ public class ResponseBodyEmitter { @@ -198,11 +198,10 @@ public class ResponseBodyEmitter {
this.handler.send(object, mediaType);
}
catch (IOException ex) {
this.sendFailed = true;
this.ioErrorOnSend = true;
throw ex;
}
catch (Throwable ex) {
this.sendFailed = true;
throw new IllegalStateException("Failed to send " + object, ex);
}
}
@ -235,11 +234,10 @@ public class ResponseBodyEmitter { @@ -235,11 +234,10 @@ public class ResponseBodyEmitter {
this.handler.send(items);
}
catch (IOException ex) {
this.sendFailed = true;
this.ioErrorOnSend = true;
throw ex;
}
catch (Throwable ex) {
this.sendFailed = true;
throw new IllegalStateException("Failed to send " + items, ex);
}
}
@ -257,8 +255,8 @@ public class ResponseBodyEmitter { @@ -257,8 +255,8 @@ public class ResponseBodyEmitter {
* related events such as an error while {@link #send(Object) sending}.
*/
public synchronized void complete() {
// Ignore, after send failure
if (this.sendFailed) {
// Ignore complete after IO failure on send
if (this.ioErrorOnSend) {
return;
}
this.complete = true;
@ -279,8 +277,8 @@ public class ResponseBodyEmitter { @@ -279,8 +277,8 @@ public class ResponseBodyEmitter {
* {@link #send(Object) sending}.
*/
public synchronized void completeWithError(Throwable ex) {
// Ignore, after send failure
if (this.sendFailed) {
// Ignore complete after IO failure on send
if (this.ioErrorOnSend) {
return;
}
this.complete = true;

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

@ -92,10 +92,9 @@ public class ResponseBodyEmitterTests { @@ -92,10 +92,9 @@ public class ResponseBodyEmitterTests {
}
@Test
void sendFailsAfterComplete() throws Exception {
void sendFailsAfterComplete() {
this.emitter.complete();
assertThatIllegalStateException().isThrownBy(() ->
this.emitter.send("foo"));
assertThatIllegalStateException().isThrownBy(() -> this.emitter.send("foo"));
}
@Test
@ -143,14 +142,47 @@ public class ResponseBodyEmitterTests { @@ -143,14 +142,47 @@ public class ResponseBodyEmitterTests {
verify(this.handler).onCompletion(any());
verifyNoMoreInteractions(this.handler);
IOException failure = new IOException();
willThrow(failure).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
assertThatIOException().isThrownBy(() ->
this.emitter.send("foo", MediaType.TEXT_PLAIN));
willThrow(new IOException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
assertThatIOException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
verifyNoMoreInteractions(this.handler);
}
@Test // gh-30687
void completeIgnoredAfterIOException() throws Exception {
this.emitter.initialize(this.handler);
verify(this.handler).onTimeout(any());
verify(this.handler).onError(any());
verify(this.handler).onCompletion(any());
verifyNoMoreInteractions(this.handler);
willThrow(new IOException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
assertThatIOException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
verifyNoMoreInteractions(this.handler);
this.emitter.complete();
verifyNoMoreInteractions(this.handler);
}
@Test // gh-30687
void completeAfterNonIOException() throws Exception {
this.emitter.initialize(this.handler);
verify(this.handler).onTimeout(any());
verify(this.handler).onError(any());
verify(this.handler).onCompletion(any());
verifyNoMoreInteractions(this.handler);
willThrow(new IllegalStateException()).given(this.handler).send("foo", MediaType.TEXT_PLAIN);
assertThatIllegalStateException().isThrownBy(() -> this.emitter.send("foo", MediaType.TEXT_PLAIN));
verify(this.handler).send("foo", MediaType.TEXT_PLAIN);
verifyNoMoreInteractions(this.handler);
this.emitter.complete();
verify(this.handler).complete();
verifyNoMoreInteractions(this.handler);
}
@Test
void onTimeoutBeforeHandlerInitialized() throws Exception {
Runnable runnable = mock();

Loading…
Cancel
Save