Fixes issue #946 "Warn user to increase max heap size" (that is, when an out of memory error occurs)

Patch by: tobyr, bruce
Review by: bruce


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@978 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
index 5300345..8a52d2a 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -25,6 +25,10 @@
  */
 public abstract class AbstractTreeLogger implements TreeLogger {
 
+  // This message is package-protected so that the unit test can access it.
+  static final String OUT_OF_MEMORY_MSG = "Out of memory; to increase the "
+      + "amount of memory, use the -Xmx flag at startup (java -Xmx128M ...)";
+
   private static class UncommittedBranchData {
 
     public final Throwable caught;
@@ -140,12 +144,23 @@
     //
     childLogger.parent = this;
 
-    // We can avoid committing this branch entry until and unless a some
+    // We can avoid committing this branch entry until and unless some
     // child (or grandchild) tries to log something that is loggable,
     // in which case there will be cascading commits of the parent branches.
     //
     childLogger.uncommitted = new UncommittedBranchData(type, msg, caught);
 
+    // This logic is intertwined with log(). If a log message is associated
+    // with an out-of-memory condition, then we turn it into a branch,
+    // so this method can be called directly from log(). It is of course
+    // also possible for someone to call branch() directly. In either case, we
+    // (1) turn the original message into an ERROR and
+    // (2) drop an extra log message that explains how to recover
+    if (causedByOutOfMemory(caught)) {
+      type = TreeLogger.ERROR;
+      childLogger.log(type, OUT_OF_MEMORY_MSG, null);
+    }
+
     // Decide whether we want to log the branch message eagerly or lazily.
     //
     if (isLoggable(type)) {
@@ -182,6 +197,13 @@
       msg = "(Null log message)";
     }
 
+    // If this log message is caused by being out of memory, we
+    // provide a little extra help by creating a child log message.
+    if (causedByOutOfMemory(caught)) {
+      branch(TreeLogger.ERROR, msg, caught);
+      return;
+    }
+
     int childIndex = allocateNextChildIndex();
     if (isLoggable(type)) {
       commitMyBranchEntryInMyParentLogger();
@@ -234,6 +256,25 @@
   }
 
   /**
+   * Scans <code>t</code> and its causes for {@link OutOfMemoryError}.
+   * 
+   * @param t a possibly null {@link Throwable}
+   * @return true if {@link OutOfMemoryError} appears anywhere in the cause list
+   *         or if <code>t</code> is an {@link OutOfMemoryError}.
+   */
+  private boolean causedByOutOfMemory(Throwable t) {
+
+    while (t != null) {
+      if (t instanceof OutOfMemoryError) {
+        return true;
+      }
+      t = t.getCause();
+    }
+
+    return false;
+  }
+
+  /**
    * Commits the branch after ensuring that the parent logger (if there is one)
    * has been committed first.
    */
diff --git a/dev/core/test/com/google/gwt/dev/util/log/AbstractTreeLoggerTest.java b/dev/core/test/com/google/gwt/dev/util/log/AbstractTreeLoggerTest.java
new file mode 100644
index 0000000..8a01351
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/util/log/AbstractTreeLoggerTest.java
@@ -0,0 +1,93 @@
+/*

+ * Copyright 2007 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.dev.util.log;

+

+import com.google.gwt.core.ext.TreeLogger;

+

+import junit.framework.TestCase;

+

+import java.io.PrintWriter;

+import java.io.StringWriter;

+

+/**

+ * Tests the <code>AbstractTreeLogger</code>.

+ */

+public class AbstractTreeLoggerTest extends TestCase {

+

+  /**

+   * We handle out-of-memory conditions specially in the logger to provide more

+   * useful log output. It does some slightly weird stuff like turning a regular

+   * log() into a branch(), so this test makes sure that doesn't break anything.

+   */

+  public void testOutOfMemoryLoggerCommitOrderForLog() {

+    StringWriter sw = new StringWriter();

+    PrintWriter pw = new PrintWriter(sw, true);

+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(pw);

+    logger.setMaxDetail(TreeLogger.WARN);

+

+    final String tstDbgStr = "TEST-DEBUG-STRING";

+    final String tstErrStr = "TEST-ERROR-STRING";

+

+    // Emit something that's low-priority and wouldn't show up normally unless

+    // it had a higher-priority child log event.

+    TreeLogger branch = logger.branch(TreeLogger.DEBUG, tstDbgStr, null);

+    assertEquals(-1, sw.toString().indexOf(tstDbgStr));

+

+    // Emit something that's low-priority but that also has a OOM.

+    branch.log(TreeLogger.ERROR, tstErrStr, new OutOfMemoryError());

+

+    // Make sure both are now there, in the right order.

+    int posTstDbgStr = sw.toString().indexOf(tstDbgStr);

+    int posTstErrStr = sw.toString().indexOf(tstErrStr);

+    int posOutOfMemory = sw.toString().indexOf(

+        AbstractTreeLogger.OUT_OF_MEMORY_MSG);

+    assertTrue(posTstDbgStr != -1);

+    assertTrue(posTstErrStr != -1);

+    assertTrue(posOutOfMemory != -1);

+    assertTrue(posTstDbgStr < posTstErrStr);

+    assertTrue(posTstErrStr < posOutOfMemory);

+  }

+

+  /**

+   * Low-priority branch points don't actually show low-priority messages unless

+   * they (later) get a child that is loggable.

+   */

+  public void testLazyBranchCommit() {

+    StringWriter sw = new StringWriter();

+    PrintWriter pw = new PrintWriter(sw, true);

+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(pw);

+    logger.setMaxDetail(TreeLogger.WARN);

+

+    final String tstDbgStr = "TEST-DEBUG-STRING";

+    final String tstErrStr = "TEST-ERROR-STRING";

+

+    // Emit something that's low-priority and wouldn't show up normally unless

+    // it had a higher-priority child log event.

+    TreeLogger branch = logger.branch(TreeLogger.DEBUG, tstDbgStr, null);

+    assertEquals(-1, sw.toString().indexOf(tstDbgStr));

+

+    // Emit something that's high-priority and will cause both to show up.

+    branch.log(TreeLogger.ERROR, tstErrStr, null);

+

+    // Make sure both are now there, in the right order.

+    int posTstDbgStr = sw.toString().indexOf(tstDbgStr);

+    int posTstErrStr = sw.toString().indexOf(tstErrStr);

+    assertTrue(posTstDbgStr != -1);

+    assertTrue(posTstErrStr != -1);

+    assertTrue(posTstDbgStr < posTstErrStr);

+  }

+

+}