AbstractListenerWriteFlushProcessor: Ensure the last flush will be performed

When writing Publisher<Publisher<T>>, a flush operation is performed onComplete
for every Publisher. If the flush operation is not able to be performed immediately
it will be retried before starting to process data provided by the next Publisher.
For the last Publisher the implementation needs to ensure that the flush will
be performed only then whole operation will complete.

Issue: SPR-15949
This commit is contained in:
Violeta Georgieva 2017-09-14 01:13:14 +03:00 committed by Brian Clozel
parent 2510db0683
commit ec2218c967
3 changed files with 116 additions and 28 deletions

View File

@ -124,6 +124,29 @@ public abstract class AbstractListenerWriteFlushProcessor<T> implements Processo
*/ */
protected abstract void flush() throws IOException; protected abstract void flush() throws IOException;
/**
* Whether writing is possible.
*/
protected abstract boolean isWritePossible();
/**
* Whether flushing is pending.
*/
protected abstract boolean isFlushPending();
/**
* Listeners can call this to notify when flushing is possible.
*/
protected final void onFlushPossible() {
this.state.get().onFlushPossible(this);
}
private void flushIfPossible() {
if (isWritePossible()) {
onFlushPossible();
}
}
private boolean changeState(State oldState, State newState) { private boolean changeState(State oldState, State newState) {
return this.state.compareAndSet(oldState, newState); return this.state.compareAndSet(oldState, newState);
@ -181,7 +204,12 @@ public abstract class AbstractListenerWriteFlushProcessor<T> implements Processo
return; return;
} }
if (processor.subscriberCompleted) { if (processor.subscriberCompleted) {
if (processor.changeState(this, COMPLETED)) { if (processor.isFlushPending()) {
// Ensure the final flush
processor.changeState(this, FLUSHING);
processor.flushIfPossible();
}
else if (processor.changeState(this, COMPLETED)) {
processor.resultPublisher.publishComplete(); processor.resultPublisher.publishComplete();
} }
} }
@ -198,6 +226,28 @@ public abstract class AbstractListenerWriteFlushProcessor<T> implements Processo
} }
}, },
FLUSHING {
public <T> void onFlushPossible(AbstractListenerWriteFlushProcessor<T> processor) {
try {
processor.flush();
}
catch (IOException ex) {
processor.flushingFailed(ex);
return;
}
if (processor.changeState(this, COMPLETED)) {
processor.resultPublisher.publishComplete();
}
}
public <T> void onNext(AbstractListenerWriteFlushProcessor<T> processor, Publisher<? extends T> publisher) {
// ignore
}
@Override
public <T> void onComplete(AbstractListenerWriteFlushProcessor<T> processor) {
// ignore
}
},
COMPLETED { COMPLETED {
@Override @Override
public <T> void onNext(AbstractListenerWriteFlushProcessor<T> processor, Publisher<? extends T> publisher) { public <T> void onNext(AbstractListenerWriteFlushProcessor<T> processor, Publisher<? extends T> publisher) {
@ -235,6 +285,10 @@ public abstract class AbstractListenerWriteFlushProcessor<T> implements Processo
// ignore // ignore
} }
public <T> void onFlushPossible(AbstractListenerWriteFlushProcessor<T> processor) {
// ignore
}
private static class WriteSubscriber implements Subscriber<Void> { private static class WriteSubscriber implements Subscriber<Void> {

View File

@ -18,7 +18,6 @@ package org.springframework.http.server.reactive;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -52,6 +51,8 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
private final HttpServletResponse response; private final HttpServletResponse response;
private final ServletOutputStream outputStream;
private final int bufferSize; private final int bufferSize;
@Nullable @Nullable
@ -73,6 +74,7 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
Assert.isTrue(bufferSize > 0, "Buffer size must be greater than 0"); Assert.isTrue(bufferSize > 0, "Buffer size must be greater than 0");
this.response = response; this.response = response;
this.outputStream = response.getOutputStream();
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
asyncContext.addListener(new ResponseAsyncListener()); asyncContext.addListener(new ResponseAsyncListener());
@ -147,7 +149,7 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
* @return the number of bytes written * @return the number of bytes written
*/ */
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
ServletOutputStream outputStream = response.getOutputStream(); ServletOutputStream outputStream = this.outputStream;
InputStream input = dataBuffer.asInputStream(); InputStream input = dataBuffer.asInputStream();
int bytesWritten = 0; int bytesWritten = 0;
byte[] buffer = new byte[this.bufferSize]; byte[] buffer = new byte[this.bufferSize];
@ -160,7 +162,7 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
} }
private void flush() throws IOException { private void flush() throws IOException {
ServletOutputStream outputStream = this.response.getOutputStream(); ServletOutputStream outputStream = this.outputStream;
if (outputStream.isReady()) { if (outputStream.isReady()) {
try { try {
outputStream.flush(); outputStream.flush();
@ -176,6 +178,10 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
} }
} }
private boolean isWritePossible() {
return this.outputStream.isReady();
}
private final class ResponseAsyncListener implements AsyncListener { private final class ResponseAsyncListener implements AsyncListener {
@ -233,6 +239,12 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
if (processor != null) { if (processor != null) {
processor.onWritePossible(); processor.onWritePossible();
} }
else {
ResponseBodyFlushProcessor flushProcessor = bodyFlushProcessor;
if (flushProcessor != null) {
flushProcessor.onFlushPossible();
}
}
} }
@Override @Override
@ -242,6 +254,13 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
processor.cancel(); processor.cancel();
processor.onError(ex); processor.onError(ex);
} }
else {
ResponseBodyFlushProcessor flushProcessor = bodyFlushProcessor;
if (flushProcessor != null) {
flushProcessor.cancel();
flushProcessor.onError(ex);
}
}
} }
} }
@ -250,15 +269,9 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
@Override @Override
protected Processor<? super DataBuffer, Void> createWriteProcessor() { protected Processor<? super DataBuffer, Void> createWriteProcessor() {
try { ResponseBodyProcessor processor = new ResponseBodyProcessor();
ServletOutputStream outputStream = response.getOutputStream(); bodyProcessor = processor;
ResponseBodyProcessor processor = new ResponseBodyProcessor(outputStream); return processor;
bodyProcessor = processor;
return processor;
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
} }
@Override @Override
@ -268,20 +281,24 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
} }
ServletServerHttpResponse.this.flush(); ServletServerHttpResponse.this.flush();
} }
@Override
protected boolean isWritePossible() {
return ServletServerHttpResponse.this.isWritePossible();
}
@Override
protected boolean isFlushPending() {
return flushOnNext;
}
} }
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> { private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
private final ServletOutputStream outputStream;
public ResponseBodyProcessor(ServletOutputStream outputStream) {
this.outputStream = outputStream;
}
@Override @Override
protected boolean isWritePossible() { protected boolean isWritePossible() {
return this.outputStream.isReady(); return ServletServerHttpResponse.this.isWritePossible();
} }
@Override @Override
@ -306,7 +323,7 @@ public class ServletServerHttpResponse extends AbstractListenerServerHttpRespons
} }
flush(); flush();
} }
boolean ready = this.outputStream.isReady(); boolean ready = ServletServerHttpResponse.this.isWritePossible();
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace("write: " + dataBuffer + " ready: " + ready); this.logger.trace("write: " + dataBuffer + " ready: " + ready);
} }

View File

@ -147,8 +147,20 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
return new ResponseBodyProcessor(this.responseChannel); return new ResponseBodyProcessor(this.responseChannel);
} }
private boolean isWritePossible() {
if (this.responseChannel == null) {
this.responseChannel = this.exchange.getResponseChannel();
}
if (this.responseChannel.isWriteResumed()) {
return true;
} else {
this.responseChannel.resumeWrites();
return false;
}
}
private static class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
private final StreamSinkChannel channel; private final StreamSinkChannel channel;
@ -164,12 +176,7 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
@Override @Override
protected boolean isWritePossible() { protected boolean isWritePossible() {
if (this.channel.isWriteResumed()) { return UndertowServerHttpResponse.this.isWritePossible();
return true;
} else {
this.channel.resumeWrites();
return false;
}
} }
@Override @Override
@ -264,6 +271,16 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
cancel(); cancel();
onError(t); onError(t);
} }
@Override
protected boolean isWritePossible() {
return UndertowServerHttpResponse.this.isWritePossible();
}
@Override
protected boolean isFlushPending() {
return false;
}
} }
} }