Skip to content

Commit

Permalink
Add locking in ServletResponse#flushBuffer
Browse files Browse the repository at this point in the history
In addition to using the ServletOutputStream, it's also possible to call
ServletResponse#flushBuffer, so the ServletOutputStream wrapper logic needs
to apply there as well.

See gh-32340
  • Loading branch information
rstoyanchev committed Mar 4, 2024
1 parent 516a203 commit 4b96cd2
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,11 @@ public void setAsyncWebRequest(StandardServletAsyncWebRequest asyncWebRequest) {
}

@Override
public ServletOutputStream getOutputStream() {
public ServletOutputStream getOutputStream() throws IOException {
if (this.outputStream == null) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
this.outputStream = new LifecycleServletOutputStream(
(HttpServletResponse) getResponse(), this.asyncWebRequest);
ServletOutputStream delegate = getResponse().getOutputStream();
this.outputStream = new LifecycleServletOutputStream(delegate, this);
}
return this.outputStream;
}
Expand All @@ -258,6 +258,46 @@ public PrintWriter getWriter() throws IOException {
}
return this.writer;
}

@Override
public void flushBuffer() throws IOException {
obtainLockAndCheckState();
try {
getResponse().flushBuffer();
}
catch (IOException ex) {
handleIOException(ex, "ServletResponse failed to flushBuffer");
}
finally {
releaseLock();
}
}

private void obtainLockAndCheckState() throws AsyncRequestNotUsableException {
Assert.notNull(this.asyncWebRequest, "Not initialized");
if (this.asyncWebRequest.state != State.NEW) {
this.asyncWebRequest.stateLock.lock();
if (this.asyncWebRequest.state != State.ASYNC) {
this.asyncWebRequest.stateLock.unlock();
throw new AsyncRequestNotUsableException("Response not usable after " +
(this.asyncWebRequest.state == State.COMPLETED ?
"async request completion" : "onError notification") + ".");
}
}
}

void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
Assert.notNull(this.asyncWebRequest, "Not initialized");
this.asyncWebRequest.transitionToErrorState();
throw new AsyncRequestNotUsableException(msg, ex);
}

void releaseLock() {
Assert.notNull(this.asyncWebRequest, "Not initialized");
if (this.asyncWebRequest.state != State.NEW) {
this.asyncWebRequest.stateLock.unlock();
}
}
}


Expand All @@ -267,113 +307,80 @@ public PrintWriter getWriter() throws IOException {
*/
private static final class LifecycleServletOutputStream extends ServletOutputStream {

private final HttpServletResponse delegate;

private final StandardServletAsyncWebRequest asyncWebRequest;
private final ServletOutputStream delegate;

private LifecycleServletOutputStream(
HttpServletResponse delegate, StandardServletAsyncWebRequest asyncWebRequest) {
private final LifecycleHttpServletResponse response;

private LifecycleServletOutputStream(ServletOutputStream delegate, LifecycleHttpServletResponse response) {
this.delegate = delegate;
this.asyncWebRequest = asyncWebRequest;
this.response = response;
}

@Override
public boolean isReady() {
return false;
return this.delegate.isReady();
}

@Override
public void setWriteListener(WriteListener writeListener) {
throw new UnsupportedOperationException();
this.delegate.setWriteListener(writeListener);
}

@Override
public void write(int b) throws IOException {
obtainLockAndCheckState();
this.response.obtainLockAndCheckState();
try {
this.delegate.getOutputStream().write(b);
this.delegate.write(b);
}
catch (IOException ex) {
handleIOException(ex, "ServletOutputStream failed to write");
this.response.handleIOException(ex, "ServletOutputStream failed to write");
}
finally {
releaseLock();
this.response.releaseLock();
}
}

public void write(byte[] buf, int offset, int len) throws IOException {
obtainLockAndCheckState();
this.response.obtainLockAndCheckState();
try {
this.delegate.getOutputStream().write(buf, offset, len);
this.delegate.write(buf, offset, len);
}
catch (IOException ex) {
handleIOException(ex, "ServletOutputStream failed to write");
this.response.handleIOException(ex, "ServletOutputStream failed to write");
}
finally {
releaseLock();
this.response.releaseLock();
}
}

@Override
public void flush() throws IOException {
obtainLockAndCheckState();
this.response.obtainLockAndCheckState();
try {
this.delegate.getOutputStream().flush();
this.delegate.flush();
}
catch (IOException ex) {
handleIOException(ex, "ServletOutputStream failed to flush");
this.response.handleIOException(ex, "ServletOutputStream failed to flush");
}
finally {
releaseLock();
this.response.releaseLock();
}
}

@Override
public void close() throws IOException {
obtainLockAndCheckState();
this.response.obtainLockAndCheckState();
try {
this.delegate.getOutputStream().close();
this.delegate.close();
}
catch (IOException ex) {
handleIOException(ex, "ServletOutputStream failed to close");
this.response.handleIOException(ex, "ServletOutputStream failed to close");
}
finally {
releaseLock();
}
}

private void obtainLockAndCheckState() throws AsyncRequestNotUsableException {
if (state() != State.NEW) {
stateLock().lock();
if (state() != State.ASYNC) {
stateLock().unlock();
throw new AsyncRequestNotUsableException("Response not usable after " +
(state() == State.COMPLETED ?
"async request completion" : "onError notification") + ".");
}
this.response.releaseLock();
}
}

private void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
this.asyncWebRequest.transitionToErrorState();
throw new AsyncRequestNotUsableException(msg, ex);
}

private void releaseLock() {
if (state() != State.NEW) {
stateLock().unlock();
}
}

private State state() {
return this.asyncWebRequest.state;
}

private Lock stateLock() {
return this.asyncWebRequest.stateLock;
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,14 +488,11 @@ private void startAsyncProcessing(Object[] processingContext) {
}

this.asyncWebRequest.startAsync();
if (logger.isDebugEnabled()) {
logger.debug("Started async request");
}
}

private static String formatUri(AsyncWebRequest asyncWebRequest) {
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
return (request != null ? request.getRequestURI() : "servlet container");
return (request != null ? "\"" + request.getRequestURI() + "\"" : "servlet container");
}


Expand Down

0 comments on commit 4b96cd2

Please sign in to comment.