Author: markt Date: Tue Jan 16 20:09:12 2018 New Revision: 1821301 URL: http://svn.apache.org/viewvc?rev=1821301&view=rev Log: Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=61993 Improve handling for ByteChunk and CharChunk instances that grow close to the maximum size allowed by the JRE.
Modified: tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/AbstractChunk.java tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/CharChunk.java tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestByteChunk.java tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestCharChunk.java tomcat/tc8.5.x/trunk/webapps/docs/changelog.xml Modified: tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/AbstractChunk.java URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/AbstractChunk.java?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/AbstractChunk.java (original) +++ tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/AbstractChunk.java Tue Jan 16 20:09:12 2018 @@ -25,17 +25,53 @@ public abstract class AbstractChunk impl private static final long serialVersionUID = 1L; + /* + * JVMs may limit the maximum array size to slightly less than + * Integer.MAX_VALUE. On markt's desktop the limit is MAX_VALUE - 2. + * Comments in the JRE source code for ArrayList and other classes indicate + * that it may be as low as MAX_VALUE - 8 on some systems. + */ + public static final int ARRAY_MAX_SIZE = Integer.MAX_VALUE - 8; private int hashCode = 0; protected boolean hasHashCode = false; protected boolean isSet; + private int limit = -1; + protected int start; protected int end; /** + * Maximum amount of data in this buffer. If -1 or not set, the buffer will + * grow to {{@link #ARRAY_MAX_SIZE}. Can be smaller than the current buffer + * size ( which will not shrink ). When the limit is reached, the buffer + * will be flushed (if out is set) or throw exception. + * + * @param limit The new limit + */ + public void setLimit(int limit) { + this.limit = limit; + } + + + public int getLimit() { + return limit; + } + + + protected int getLimitInternal() { + if (limit > 0) { + return limit; + } else { + return ARRAY_MAX_SIZE; + } + } + + + /** * @return the start position of the data in the buffer */ public int getStart() { Modified: tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java (original) +++ tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/ByteChunk.java Tue Jan 16 20:09:12 2018 @@ -128,10 +128,6 @@ public final class ByteChunk extends Abs // byte[] private byte[] buff; - // -1: grow indefinitely - // maximum amount to be cached - private int limit = -1; - private ByteInputChannel in = null; private ByteOutputChannel out = null; @@ -166,7 +162,7 @@ public final class ByteChunk extends Abs if (buff == null || buff.length < initial) { buff = new byte[initial]; } - this.limit = limit; + setLimit(limit); start = 0; end = 0; isSet = true; @@ -220,24 +216,6 @@ public final class ByteChunk extends Abs /** - * Maximum amount of data in this buffer. If -1 or not set, the buffer will - * grow indefinitely. Can be smaller than the current buffer size ( which - * will not shrink ). When the limit is reached, the buffer will be flushed - * ( if out is set ) or throw exception. - * - * @param limit The new limit - */ - public void setLimit(int limit) { - this.limit = limit; - } - - - public int getLimit() { - return limit; - } - - - /** * When the buffer is empty, read the data from the input channel. * * @param in The input channel @@ -263,9 +241,10 @@ public final class ByteChunk extends Abs public void append(byte b) throws IOException { makeSpace(1); + int limit = getLimitInternal(); // couldn't make space - if (limit > 0 && end >= limit) { + if (end >= limit) { flushBuffer(); } buff[end++] = b; @@ -288,14 +267,7 @@ public final class ByteChunk extends Abs public void append(byte src[], int off, int len) throws IOException { // will grow, up to limit makeSpace(len); - - // if we don't have limit: makeSpace can grow as it wants - if (limit < 0) { - // assert: makeSpace made enough space - System.arraycopy(src, off, buff, end, len); - end += len; - return; - } + int limit = getLimitInternal(); // Optimize on a common case. // If the buffer is empty and the source is going to fill up all the @@ -306,23 +278,19 @@ public final class ByteChunk extends Abs return; } - // if we have limit and we're below + // if we are below the limit if (len <= limit - end) { - // makeSpace will grow the buffer to the limit, - // so we have space System.arraycopy(src, off, buff, end, len); - end += len; return; } - // need more space than we can afford, need to flush - // buffer + // Need more space than we can afford, need to flush buffer. - // the buffer is already at ( or bigger than ) limit + // The buffer is already at (or bigger than) limit. // We chunk the data into slices fitting in the buffer limit, although - // if the data is written directly if it doesn't fit + // if the data is written directly if it doesn't fit. int avail = limit - end; System.arraycopy(src, off, buff, end, avail); @@ -339,7 +307,6 @@ public final class ByteChunk extends Abs System.arraycopy(src, (off + len) - remain, buff, end, remain); end += remain; - } @@ -354,14 +321,7 @@ public final class ByteChunk extends Abs // will grow, up to limit makeSpace(len); - - // if we don't have limit: makeSpace can grow as it wants - if (limit < 0) { - // assert: makeSpace made enough space - from.get(buff, end, len); - end += len; - return; - } + int limit = getLimitInternal(); // Optimize on a common case. // If the buffer is empty and the source is going to fill up all the @@ -490,7 +450,7 @@ public final class ByteChunk extends Abs public void flushBuffer() throws IOException { // assert out!=null if (out == null) { - throw new IOException("Buffer overflow, no sink " + limit + " " + buff.length); + throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length); } out.realWriteBytes(buff, start, end - start); end = start; @@ -499,18 +459,20 @@ public final class ByteChunk extends Abs /** * Make space for len bytes. If len is small, allocate a reserve space too. - * Never grow bigger than limit. + * Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}. * * @param count The size */ public void makeSpace(int count) { byte[] tmp = null; - int newSize; - int desiredSize = end + count; + int limit = getLimitInternal(); + + long newSize; + long desiredSize = end + count; // Can't grow above the limit - if (limit > 0 && desiredSize > limit) { + if (desiredSize > limit) { desiredSize = limit; } @@ -518,25 +480,25 @@ public final class ByteChunk extends Abs if (desiredSize < 256) { desiredSize = 256; // take a minimum } - buff = new byte[desiredSize]; + buff = new byte[(int) desiredSize]; } - // limit < buf.length ( the buffer is already big ) + // limit < buf.length (the buffer is already big) // or we already have space XXX if (desiredSize <= buff.length) { return; } // grow in larger chunks - if (desiredSize < 2 * buff.length) { - newSize = buff.length * 2; + if (desiredSize < 2L * buff.length) { + newSize = buff.length * 2L; } else { - newSize = buff.length * 2 + count; + newSize = buff.length * 2L + count; } - if (limit > 0 && newSize > limit) { + if (newSize > limit) { newSize = limit; } - tmp = new byte[newSize]; + tmp = new byte[(int) newSize]; // Compacts buffer System.arraycopy(buff, start, tmp, 0, end - start); Modified: tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/CharChunk.java URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/CharChunk.java?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/CharChunk.java (original) +++ tomcat/tc8.5.x/trunk/java/org/apache/tomcat/util/buf/CharChunk.java Tue Jan 16 20:09:12 2018 @@ -70,10 +70,6 @@ public final class CharChunk extends Abs // char[] private char[] buff; - // -1: grow indefinitely - // maximum amount to be cached - private int limit = -1; - private CharInputChannel in = null; private CharOutputChannel out = null; @@ -104,7 +100,7 @@ public final class CharChunk extends Abs if (buff == null || buff.length < initial) { buff = new char[initial]; } - this.limit = limit; + setLimit(limit); start = 0; end = 0; isSet = true; @@ -145,24 +141,6 @@ public final class CharChunk extends Abs /** - * Maximum amount of data in this buffer. If -1 or not set, the buffer will - * grow indefinitely. Can be smaller than the current buffer size ( which - * will not shrink ). When the limit is reached, the buffer will be flushed - * ( if out is set ) or throw exception. - * - * @param limit The new limit - */ - public void setLimit(int limit) { - this.limit = limit; - } - - - public int getLimit() { - return limit; - } - - - /** * When the buffer is empty, read the data from the input channel. * * @param in The input channel @@ -188,9 +166,10 @@ public final class CharChunk extends Abs public void append(char b) throws IOException { makeSpace(1); + int limit = getLimitInternal(); // couldn't make space - if (limit > 0 && end >= limit) { + if (end >= limit) { flushBuffer(); } buff[end++] = b; @@ -213,14 +192,7 @@ public final class CharChunk extends Abs public void append(char src[], int off, int len) throws IOException { // will grow, up to limit makeSpace(len); - - // if we don't have limit: makeSpace can grow as it wants - if (limit < 0) { - // assert: makeSpace made enough space - System.arraycopy(src, off, buff, end, len); - end += len; - return; - } + int limit = getLimitInternal(); // Optimize on a common case. // If the buffer is empty and the source is going to fill up all the @@ -231,27 +203,22 @@ public final class CharChunk extends Abs return; } - // if we have limit and we're below + // if we are below the limit if (len <= limit - end) { - // makeSpace will grow the buffer to the limit, - // so we have space System.arraycopy(src, off, buff, end, len); - end += len; return; } - // need more space than we can afford, need to flush - // buffer + // Need more space than we can afford, need to flush buffer. - // the buffer is already at ( or bigger than ) limit + // The buffer is already at (or bigger than) limit. // Optimization: - // If len-avail < length ( i.e. after we fill the buffer with - // what we can, the remaining will fit in the buffer ) we'll just - // copy the first part, flush, then copy the second part - 1 write - // and still have some space for more. We'll still have 2 writes, but - // we write more on the first. + // If len-avail < length (i.e. after we fill the buffer with what we + // can, the remaining will fit in the buffer) we'll just copy the first + // part, flush, then copy the second part - 1 write and still have some + // space for more. We'll still have 2 writes, but we write more on the first. if (len + end < 2 * limit) { /* @@ -304,14 +271,7 @@ public final class CharChunk extends Abs // will grow, up to limit makeSpace(len); - - // if we don't have limit: makeSpace can grow as it wants - if (limit < 0) { - // assert: makeSpace made enough space - s.getChars(off, off + len, buff, end); - end += len; - return; - } + int limit = getLimitInternal(); int sOff = off; int sEnd = off + len; @@ -374,7 +334,7 @@ public final class CharChunk extends Abs public void flushBuffer() throws IOException { // assert out!=null if (out == null) { - throw new IOException("Buffer overflow, no sink " + limit + " " + buff.length); + throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length); } out.realWriteChars(buff, start, end - start); end = start; @@ -383,18 +343,20 @@ public final class CharChunk extends Abs /** * Make space for len chars. If len is small, allocate a reserve space too. - * Never grow bigger than limit. + * Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}. * * @param count The size */ public void makeSpace(int count) { char[] tmp = null; - int newSize; - int desiredSize = end + count; + int limit = getLimitInternal(); + + long newSize; + long desiredSize = end + count; // Can't grow above the limit - if (limit > 0 && desiredSize > limit) { + if (desiredSize > limit) { desiredSize = limit; } @@ -402,25 +364,25 @@ public final class CharChunk extends Abs if (desiredSize < 256) { desiredSize = 256; // take a minimum } - buff = new char[desiredSize]; + buff = new char[(int) desiredSize]; } - // limit < buf.length ( the buffer is already big ) + // limit < buf.length (the buffer is already big) // or we already have space XXX if (desiredSize <= buff.length) { return; } // grow in larger chunks - if (desiredSize < 2 * buff.length) { - newSize = buff.length * 2; + if (desiredSize < 2L * buff.length) { + newSize = buff.length * 2L; } else { - newSize = buff.length * 2 + count; + newSize = buff.length * 2L + count; } - if (limit > 0 && newSize > limit) { + if (newSize > limit) { newSize = limit; } - tmp = new char[newSize]; + tmp = new char[(int) newSize]; // Some calling code assumes buffer will not be compacted System.arraycopy(buff, 0, tmp, 0, end); Modified: tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestByteChunk.java URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestByteChunk.java?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestByteChunk.java (original) +++ tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestByteChunk.java Tue Jan 16 20:09:12 2018 @@ -16,12 +16,17 @@ */ package org.apache.tomcat.util.buf; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import org.apache.tomcat.util.buf.ByteChunk.ByteOutputChannel; + /** * Test cases for {@link ByteChunk}. */ @@ -35,6 +40,7 @@ public class TestByteChunk { Assert.assertTrue(Arrays.equals(bytes, expected)); } + /* * Test for {@code findByte} vs. {@code indexOf} methods difference. * @@ -70,6 +76,7 @@ public class TestByteChunk { Assert.assertEquals(-1, ByteChunk.indexOf(bytes, 5, 5, 'w')); } + @Test public void testIndexOf_Char() throws UnsupportedEncodingException { byte[] bytes = "Hello\u00a0world".getBytes("ISO-8859-1"); @@ -92,6 +99,7 @@ public class TestByteChunk { Assert.assertEquals(-1, bc.indexOf('d', 0)); } + @Test public void testIndexOf_String() throws UnsupportedEncodingException { byte[] bytes = "Hello\u00a0world".getBytes("ISO-8859-1"); @@ -117,6 +125,7 @@ public class TestByteChunk { Assert.assertEquals(-1, bc.indexOf("d", 0, 1, 0)); } + @Test public void testFindBytes() throws UnsupportedEncodingException { byte[] bytes = "Hello\u00a0world".getBytes("ISO-8859-1"); @@ -133,4 +142,35 @@ public class TestByteChunk { 'e' })); Assert.assertEquals(-1, ByteChunk.findBytes(bytes, 2, 5, new byte[] { 'w' })); } + + + @Ignore // Requires a 6GB heap (on markt's desktop - YMMV) + @Test + public void testAppend() throws Exception { + ByteChunk bc = new ByteChunk(); + bc.setByteOutputChannel(new Sink()); + // Defaults to no limit + + byte data[] = new byte[32 * 1024 * 1024]; + + for (int i = 0; i < 100; i++) { + bc.append(data, 0, data.length); + } + + Assert.assertEquals(AbstractChunk.ARRAY_MAX_SIZE, bc.getBuffer().length); + } + + + public class Sink implements ByteOutputChannel { + + @Override + public void realWriteBytes(byte[] cbuf, int off, int len) throws IOException { + // NO-OP + } + + @Override + public void realWriteBytes(ByteBuffer from) throws IOException { + // NO-OP + } + } } Modified: tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestCharChunk.java URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestCharChunk.java?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestCharChunk.java (original) +++ tomcat/tc8.5.x/trunk/test/org/apache/tomcat/util/buf/TestCharChunk.java Tue Jan 16 20:09:12 2018 @@ -16,9 +16,14 @@ */ package org.apache.tomcat.util.buf; +import java.io.IOException; + import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import org.apache.tomcat.util.buf.CharChunk.CharOutputChannel; + /** * Test cases for {@link CharChunk}. */ @@ -37,6 +42,7 @@ public class TestCharChunk { Assert.assertFalse(cc.endsWith("xxtest")); } + @Test public void testIndexOf_String() { char[] chars = "Hello\u00a0world".toCharArray(); @@ -62,4 +68,29 @@ public class TestCharChunk { Assert.assertEquals(-1, cc.indexOf("d", 0, 1, 0)); } + + @Ignore // Requires an 11GB heap (on markt's desktop - YMMV) + @Test + public void testAppend() throws Exception { + CharChunk cc = new CharChunk(); + cc.setCharOutputChannel(new Sink()); + // Defaults to no limit + + char data[] = new char[32 * 1024 * 1024]; + + for (int i = 0; i < 100; i++) { + cc.append(data, 0, data.length); + } + + Assert.assertEquals(AbstractChunk.ARRAY_MAX_SIZE, cc.getBuffer().length); + } + + + public class Sink implements CharOutputChannel { + + @Override + public void realWriteChars(char[] cbuf, int off, int len) throws IOException { + // NO-OP + } + } } Modified: tomcat/tc8.5.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc8.5.x/trunk/webapps/docs/changelog.xml?rev=1821301&r1=1821300&r2=1821301&view=diff ============================================================================== --- tomcat/tc8.5.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc8.5.x/trunk/webapps/docs/changelog.xml Tue Jan 16 20:09:12 2018 @@ -45,6 +45,15 @@ issues do not "pop up" wrt. others). --> <section name="Tomcat 8.5.27 (markt)" rtext="in development"> + <subsection name="Coyote"> + <changelog> + <fix> + <bug>61993</bug>: Improve handling for <code>ByteChunk</code> and + <code>CharChunk</code> instances that grow close to the maximum size + allowed by the JRE. (markt) + </fix> + </changelog> + </subsection> </section> <section name="Tomcat 8.5.26 (markt)" rtext="release in progress"> <subsection name="Catalina"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org