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")