Check BLOB content size before trying to render it Make sure that the content returned by JGit is below the maximum size allowed for formatting/rendering with Soy. The method loader.getCachedBytes(MAX_FILE_SIZE) would return an in-memory content larger than MAX_FILE_SIZE if the the BLOB is smaller than streamFileThreshold. Parsing 100s megabytes of in-memory content using a prettyfier regex and trying to render it in HTML would result in a massive allocation of strings in the JVM heap. The overload of memory allocation may eventually result in triggering continuous 'stop-the-world' GC cycles, blocking the process for several minutes. Bug: https://github.com/google/gitiles/issues/192 Change-Id: I6ab5f367e731d67d4a5816a0beae5551106bb72b
diff --git a/java/com/google/gitiles/BlobSoyData.java b/java/com/google/gitiles/BlobSoyData.java index c505ed4..c906b4a 100644 --- a/java/com/google/gitiles/BlobSoyData.java +++ b/java/com/google/gitiles/BlobSoyData.java
@@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkState; import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; @@ -47,7 +48,7 @@ * will be displayed as binary files, even if the contents was text. For example really big XML * files may be above this limit and will get displayed as binary. */ - private static final int MAX_FILE_SIZE = 10 << 20; + @VisibleForTesting static final int MAX_FILE_SIZE = 10 << 20; private final GitilesView view; private final ObjectReader reader; @@ -70,7 +71,8 @@ String content; try { byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE); - content = !RawText.isBinary(raw) ? RawParseUtils.decode(raw) : null; + content = + (raw.length < MAX_FILE_SIZE && !RawText.isBinary(raw)) ? RawParseUtils.decode(raw) : null; } catch (LargeObjectException.OutOfMemory e) { throw e; } catch (LargeObjectException e) {
diff --git a/javatests/com/google/gitiles/PathServletTest.java b/javatests/com/google/gitiles/PathServletTest.java index c0f98b1..998ede1 100644 --- a/javatests/com/google/gitiles/PathServletTest.java +++ b/javatests/com/google/gitiles/PathServletTest.java
@@ -102,6 +102,24 @@ } @Test + public void largeFileHtml() throws Exception { + int largeContentSize = BlobSoyData.MAX_FILE_SIZE + 1; + repo.branch("master").commit().add("foo", generateContent(largeContentSize)).create(); + + Map<String, ?> data = (Map<String, ?>) buildData("/repo/+/master/foo").get("data"); + assertThat(data).containsEntry("lines", null); + assertThat(data).containsEntry("size", "" + largeContentSize); + } + + private static String generateContent(int contentSize) { + char[] str = new char[contentSize]; + for (int i = 0; i < contentSize; i++) { + str[i] = (char) ('0' + (i % 78)); + } + return new String(str); + } + + @Test public void symlinkHtml() throws Exception { final RevBlob link = repo.blob("foo"); repo.branch("master")