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); + } + +}