You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/09/13 16:05:09 UTC
[tomcat] 01/03: Avoid StackOverflowException
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 3910ab722be9b791533f8662e45cc6b5701c3177
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Sep 10 08:21:36 2021 +0100
Avoid StackOverflowException
On a fast network (e.g. localhost) the writes all complete in-line. That
leads to the CompletionHandler triggering another write, which triggers
the CompletionHandler which trigegrs another write and so on. With large
response bodies and recursive in-line writes, it is easy to trigger a
StackOverflowException.
---
.../coyote/http2/Http2AsyncUpgradeHandler.java | 127 ++++++++++++---------
webapps/docs/changelog.xml | 4 +
2 files changed, 75 insertions(+), 56 deletions(-)
diff --git a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
index 4dc268a..19c88a1 100644
--- a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
@@ -36,6 +36,7 @@ import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.SendfileState;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.net.SocketWrapperBase.BlockingMode;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionState;
public class Http2AsyncUpgradeHandler extends Http2UpgradeHandler {
@@ -363,70 +364,84 @@ public class Http2AsyncUpgradeHandler extends Http2UpgradeHandler {
protected class SendfileCompletionHandler implements CompletionHandler<Long, SendfileData> {
@Override
public void completed(Long nBytes, SendfileData sendfile) {
+ CompletionState completionState = null;
long bytesWritten = nBytes.longValue() - 9;
- sendfile.left -= bytesWritten;
- if (sendfile.left == 0) {
- try {
- sendfile.stream.getOutputBuffer().end();
- } catch (IOException e) {
- failed(e, sendfile);
+
+ /*
+ * Loop for in-line writes only. Avoids a possible stack-overflow of
+ * chained completion handlers with a long series of in-line writes.
+ */
+ do {
+ sendfile.left -= bytesWritten;
+ if (sendfile.left == 0) {
+ try {
+ sendfile.stream.getOutputBuffer().end();
+ } catch (IOException e) {
+ failed(e, sendfile);
+ }
+ return;
}
- return;
- }
- sendfile.streamReservation -= bytesWritten;
- sendfile.connectionReservation -= bytesWritten;
- sendfile.pos += bytesWritten;
- try {
- if (sendfile.connectionReservation == 0) {
- if (sendfile.streamReservation == 0) {
- int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos);
- sendfile.streamReservation = sendfile.stream.reserveWindowSize(reservation, true);
+ sendfile.streamReservation -= bytesWritten;
+ sendfile.connectionReservation -= bytesWritten;
+ sendfile.pos += bytesWritten;
+ try {
+ if (sendfile.connectionReservation == 0) {
+ if (sendfile.streamReservation == 0) {
+ int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos);
+ sendfile.streamReservation = sendfile.stream.reserveWindowSize(reservation, true);
+ }
+ sendfile.connectionReservation = reserveWindowSize(sendfile.stream, sendfile.streamReservation, true);
}
- sendfile.connectionReservation = reserveWindowSize(sendfile.stream, sendfile.streamReservation, true);
+ } catch (IOException e) {
+ failed (e, sendfile);
+ return;
}
- } catch (IOException e) {
- failed (e, sendfile);
- return;
- }
- if (log.isDebugEnabled()) {
- log.debug(sm.getString("upgradeHandler.sendfile.reservation", connectionId, sendfile.stream.getIdAsString(),
- Integer.valueOf(sendfile.connectionReservation), Integer.valueOf(sendfile.streamReservation)));
- }
-
- // connectionReservation will always be smaller than or the same as
- // streamReservation
- int frameSize = Integer.min(getMaxFrameSize(), sendfile.connectionReservation);
- boolean finished = (frameSize == sendfile.left) && sendfile.stream.getCoyoteResponse().getTrailerFields() == null;
-
- // Need to check this now since sending end of stream will change this.
- boolean writeable = sendfile.stream.canWrite();
- byte[] header = new byte[9];
- ByteUtil.setThreeBytes(header, 0, frameSize);
- header[3] = FrameType.DATA.getIdByte();
- if (finished) {
- header[4] = FLAG_END_OF_STREAM;
- sendfile.stream.sentEndOfStream();
- if (!sendfile.stream.isActive()) {
- setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet());
- }
- }
- if (writeable) {
if (log.isDebugEnabled()) {
- log.debug(sm.getString("upgradeHandler.writeBody", connectionId, sendfile.stream.getIdAsString(),
- Integer.toString(frameSize), Boolean.valueOf(finished)));
+ log.debug(sm.getString("upgradeHandler.sendfile.reservation", connectionId, sendfile.stream.getIdAsString(),
+ Integer.valueOf(sendfile.connectionReservation), Integer.valueOf(sendfile.streamReservation)));
}
- ByteUtil.set31Bits(header, 5, sendfile.stream.getIdAsInt());
- sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize);
- socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
- TimeUnit.MILLISECONDS, sendfile, SocketWrapperBase.COMPLETE_WRITE_WITH_COMPLETION,
- this, ByteBuffer.wrap(header), sendfile.mappedBuffer);
- try {
- handleAsyncException();
- } catch (IOException e) {
- failed(e, sendfile);
+
+ // connectionReservation will always be smaller than or the same as
+ // streamReservation
+ int frameSize = Integer.min(getMaxFrameSize(), sendfile.connectionReservation);
+ boolean finished = (frameSize == sendfile.left) && sendfile.stream.getCoyoteResponse().getTrailerFields() == null;
+
+ // Need to check this now since sending end of stream will change this.
+ boolean writeable = sendfile.stream.canWrite();
+ byte[] header = new byte[9];
+ ByteUtil.setThreeBytes(header, 0, frameSize);
+ header[3] = FrameType.DATA.getIdByte();
+ if (finished) {
+ header[4] = FLAG_END_OF_STREAM;
+ sendfile.stream.sentEndOfStream();
+ if (!sendfile.stream.isActive()) {
+ setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet());
+ }
}
- }
+ if (writeable) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("upgradeHandler.writeBody", connectionId, sendfile.stream.getIdAsString(),
+ Integer.toString(frameSize), Boolean.valueOf(finished)));
+ }
+ ByteUtil.set31Bits(header, 5, sendfile.stream.getIdAsInt());
+ sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize);
+ // Note: Completion handler not called in the write
+ // completes in-line. The wrote will continue via the
+ // surrounding loop.
+ completionState = socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
+ TimeUnit.MILLISECONDS, sendfile, SocketWrapperBase.COMPLETE_WRITE,
+ this, ByteBuffer.wrap(header), sendfile.mappedBuffer);
+ try {
+ handleAsyncException();
+ } catch (IOException e) {
+ failed(e, sendfile);
+ return;
+ }
+ }
+ // Update bytesWritten for start of next loop iteration
+ bytesWritten = frameSize;
+ } while (completionState == CompletionState.INLINE);
}
@Override
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 224fafd..8132f24 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -121,6 +121,10 @@
after <code>bytes</code>. Fix based on pull request <pr>449</pr> by
Thierry Guérin. (markt)
</fix>
+ <fix>
+ Correct a potential <code>StackOverflowException</code> with HTTP/2 and
+ sendfile. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org