You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by bp...@apache.org on 2007/04/18 23:15:21 UTC

svn commit: r530159 - in /db/derby/code/trunk/java: engine/org/apache/derby/impl/jdbc/ testing/ testing/org/apache/derbyTesting/functionTests/tests/memory/

Author: bpendleton
Date: Wed Apr 18 14:15:21 2007
New Revision: 530159

URL: http://svn.apache.org/viewvc?view=rev&rev=530159
Log:
DERBY-2480: getConnection leaks memory when connecting to non-existent db

This patch was contributed by Jeff Clary (jclary@actuate.com). The test
program was contributed by John Embretsen (John.Embretsen@Sun.com) based
on the original reproduction program contributed by Jeff Clary.

The issue is that repeated calls to java.sql.DriverManager.getConnection(
"jdbc:derby:C:\\DOES_NOT_EXIST") leak memory and eventually lead to an
OutOfMemoryError.

This bug is similar to DERBY-1947 in that ContextManager objects are not
getting removed from the HashSet. 

The change adds a call to cleanupOnError to EmbedConnection's constructor.

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java   (with props)
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
    db/derby/code/trunk/java/testing/build.xml

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java?view=diff&rev=530159&r1=530158&r2=530159
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java Wed Apr 18 14:15:21 2007
@@ -369,6 +369,7 @@
 			throw NO_MEM;
 		}
 		catch (Throwable t) {
+			tr.cleanupOnError(t);
 			throw handleException(t);
 		} finally {
 			restoreContextStack();

Modified: db/derby/code/trunk/java/testing/build.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/build.xml?view=diff&rev=530159&r1=530158&r2=530159
==============================================================================
--- db/derby/code/trunk/java/testing/build.xml (original)
+++ db/derby/code/trunk/java/testing/build.xml Wed Apr 18 14:15:21 2007
@@ -75,6 +75,7 @@
     <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/i18n"/> 
     <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/jdbc4"/> 
     <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/perf"/> 
+    <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/memory"/> 
   	<ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/upgradeTests"/> 
     <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/tests/largedata"/> 
     <ant dir="${derby.testing.src.dir}/${derby.testing.functest.dir}/multi/stress"/> 

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java?view=auto&rev=530159
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java Wed Apr 18 14:15:21 2007
@@ -0,0 +1,244 @@
+/**
+ * Derby - Class org.apache.derbyTesting.functionTests.tests.memory.ConnectionHandlingJunit
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.derbyTesting.functionTests.tests.memory;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.JDBC;
+
+/**
+ *  This class tests Derby's ability to handle multiple connection attempts
+ *  against one or more databases, which may or may not exist. Such repeated
+ *  connection attempts have been known to cause OutOfMemoryErrors in the past,
+ *  see for example DERBY-2480.
+ */
+public class ConnectionHandlingJunit extends BaseJDBCTestCase {
+    
+    /** Creates a new instance of this test class 
+     *  @param name The name of this test instance; may determine which test
+     *         fixture to run.
+     */
+    public ConnectionHandlingJunit(String name) {
+        super(name);
+    }
+    
+    /**
+     * Creates a Test Suite to be run by a JUnit TestRunner. The elements of
+     * the test suite may depend on the environment in which the TestRunner
+     * is running (classpath, JVM, etc.).
+     *
+     * @return JUnit test suite containing appropriate tests from this class.
+     */
+    public static Test suite() {
+        
+        TestSuite suite = new TestSuite("ConnectionHandlingJUnit");
+        
+        // Only support for java.sql.DriverManager has been implemented.
+        if (JDBC.vmSupportsJDBC2()) {
+            /* driverMgrTestConnectionsToNonexistentDb:
+             * Only support for DriverManager (JDBC2 and above) for now, for
+             * simplicity (connecting to DB and possibly also loading the driver
+             * manually, to have control over url attributes (database 
+             * creation)). This adheres to the following advice in 
+             * ...derbyTesting.junit.Connector.java:
+             * "Tests that need finer control over the connection handling
+             * should use the JDBC classes directly, such as DriverManager
+             * or DataSource."
+             */
+
+            TestCase nonExistentDbTest = new ConnectionHandlingJunit(
+                    "driverMgrTestConnectionsToNonexistentDb");
+            
+            /* run "driverMgrTestConnectionsToNonexistentDb" in embedded mode only
+             * by default, since it is not very useful to continue running in
+             * client/server mode (server in the same JVM) if the JVM's memory 
+             * resources are <i>almost</i> exhausted from the embedded test.
+             */
+            suite.addTest(nonExistentDbTest);
+            // to run the test in client/server mode, comment the above line,
+            // uncomment the next and recompile.
+            //suite.addTest(TestConfiguration.clientServerDecorator(nonExistentDbTest));
+            
+        }
+        
+        return suite;
+    }    
+
+    
+    /**
+     * <p>This fixture tries a number of times to connect to a non-existent
+     * database, in order to test Derby's ability to handle this situation
+     * without running out of resources (for example Java heap space (memory)).
+     * See 
+     * <a href="https://issues.apache.org/jira/browse/DERBY-2480">DERBY-2480</a>
+     * for details.</p>
+     * <p>This test fixture is currently not part of any large JUnit suite
+     * because <b>1)</b> the default number of connection attempts is rather
+     * large, and takes some time to complete (depending on hardware), and 
+     * <b>2)</b> if the tested Derby version is still vulnerable to DERBY-2480
+     * or similar issues the JVM will most likely run out of memory (depending
+     * on heap settings), causing subsequent tests to fail, hang or not run at
+     * all.</p>
+     * <p>
+     * <b>Note:</b> The JVM may slow down significantly (even appear to hang)
+     * before an OOME is thrown. Depending on the avaliable resources, the error
+     * may or may not be reported in the logs (derby.log, server console).</p>
+     * <p>
+     * This fixture requires java.sql.DriverManager. This is because simple
+     * and easy control of the connection handling and database creation is 
+     * desired (see implementation comments). However, the test logic itself 
+     * should also work with other connection mechanisms.</p>
+     *
+     * @throws SQLException if an unexpected exception occurs that is not
+     *         examined using assertions.
+     */
+    public void driverMgrTestConnectionsToNonexistentDb() throws SQLException {
+
+        Connection myInvalidConn = null;
+        
+        String url = getTestConfiguration().getJDBCUrl("nonexistentDatabase");
+        // Not using the regular helper methods in super class because
+        // we don't want to actually create a database, or connect to an
+        // existing one (current helper classes add ";create=true" if the DB
+        // does not exist). Hence, the JDBC driver will not necessarily be 
+        // loaded automatically.
+        loadDriver(url);
+        
+        Runtime runtime = Runtime.getRuntime();
+        double memTotalNow; // Total amount of heap memory committed to this JVM
+        
+        // ~110k attempts is enough for a 64 MB heap (prior to DERBY-2480 fix)
+        int maxCount = 130000;  // max number of connection attempts to try
+        int count = 0;  // number of connection attempts so far
+        
+        println("Trying " + maxCount + " connection attempts...");
+        
+        try {
+            while (count < maxCount) {
+
+                try {
+                    // We are expecting an exception here because we are trying to 
+                    // connect to a DB that does not exist.
+                    myInvalidConn = DriverManager.getConnection(url);
+                    // The following may happen because of changes to helper methods
+                    // such as TestConfiguration.getJDBCUrl(dbName).
+                    fail("Got connection to a DB that should not exist");
+                } catch (SQLException e) {
+                    // Expected SQLState for "database not found"...
+                    // For the client driver the generic 08004 is returned.
+                    String expectedState;
+                    if (getTestConfiguration().getJDBCClient().isEmbedded()) {
+                        // embedded driver
+                        expectedState = "XJ004";
+                        // in embedded mode, OOMEs are usually wrapped in 
+                        // SQLExceptions with SQLState 08004
+                        if (e.getSQLState().equals("08004") 
+                                && e.getMessage().matches(".*OutOfMemoryError.*")) {
+                            alarm("OutOfMemoryError after " + count 
+                                    + " connection attempts to a "
+                                    + "non-existing database!");
+                            // test should fail on next assertSQLState call,
+                            // but may not have enough resources to get that far
+                            printStackTrace(e);
+                            //fail("OutOfMemoryError: " + e.getMessage());
+                        }
+                    } else {
+                        // client driver
+                        expectedState = "08004";
+                        // with client driver, OOMEs are often wrapped in
+                        // SQLExceptions with SQLState XJ001
+                        if (e.getSQLState().equals("XJ001") 
+                                && e.getMessage().matches(".*OutOfMemoryError.*")) {
+                            alarm("OutOfMemoryError after " + count 
+                                    + " connection attempts to a "
+                                    + "non-existing database!");
+                            // test should fail on next assertSQLState call,
+                            // but may not have enough resources to do so
+                            printStackTrace(e);
+                        }
+                    }
+                    assertSQLState("Wrong SQLState for non-existent database", 
+                            expectedState, e);
+                }
+
+                count++;
+                // print informational messages (mem) if debug property is true
+                if (getTestConfiguration().isVerbose()) {
+                    if (count % 1000 == 0) {
+                        memTotalNow = runtime.totalMemory()/(double)(1024*1024);
+                        println("Iteration: " + count + "\tTotal memory (MB): " 
+                                + memTotalNow);
+                    }
+                }
+            }
+        } catch(OutOfMemoryError oome) {
+            alarm("OutOfMemory after " + count + " connection attempts!");
+            alarm(oome.getMessage());
+            throw oome;
+        }
+    }
+    
+    /**
+     * Will check if the JDBC driver has been loaded and load it if that is not 
+     * the case.
+     * Any other exception messages than "No suitable driver" on the first
+     * attempt to get the JDBC driver will result in an assertion failure.
+     * 
+     * @param url a valid connection URL for the desired JDBC driver
+     * @throws SQLException if an unexpected exception is thrown
+     */
+    private void loadDriver(String url) throws SQLException {
+        try {
+            DriverManager.getDriver(url);
+        } catch (SQLException e) {
+            // getDriver() failed, JDBC driver probably not loaded.
+            // Expecting SQLState 08001 and message "No suitable driver"...
+            assertSQLState("Unexpected SQLState from getDriver().", "08001", e);
+            assertEquals("Unexpected exception message from getDriver(), ",
+                    "No suitable driver", e.getMessage());
+            String driverClass = 
+                    getTestConfiguration().getJDBCClient().getJDBCDriverName();
+            println("Loading JDBC driver " + driverClass);
+            // load the driver
+            try {
+                Class.forName(driverClass).newInstance();
+            } catch (ClassNotFoundException cnfe) {
+                throw new SQLException("Failed to load JDBC driver '" 
+                        + driverClass + "', ClassNotFoundException: " 
+                        + cnfe.getMessage());
+            } catch (IllegalAccessException iae) {
+                throw new SQLException("Failed to load JDBC driver '" 
+                        + driverClass + "', IllegalAccessException: " 
+                        + iae.getMessage());
+            } catch (InstantiationException ie) {
+                throw new SQLException("Failed to load JDBC driver '" 
+                        + driverClass + "', InstantiationException: " 
+                        + ie.getMessage());
+            }
+        }
+    }
+
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/ConnectionHandlingJunit.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml?view=auto&rev=530159
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml Wed Apr 18 14:15:21 2007
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to you 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.
+-->
+
+<project default="memorytests" basedir="../../../../../../..">
+
+<!-- Properties -->
+
+  <!-- User settings -->
+  <property file="${user.home}/ant.properties"/>
+  <!-- Set property lib dir -->
+  <property name="properties.dir" value="tools/ant/properties"/>
+  <!-- Significant dirs -->
+  <property file="${properties.dir}/dirs.properties"/>
+  <property file="${properties.dir}/derbytesting.properties"/>
+  <property file="${user.home}/properties/derbytesting.properties"/>
+  <property file="${ant.home}/properties/derbytesting.properties"/>
+  <!-- Compiler settings -->
+  <property file="${properties.dir}/${build.compiler}.properties"/>
+  <!-- Compile-time classpath properties files -->
+  <property file="${properties.dir}/extrapath.properties"/>
+  <property file="${properties.dir}/derbytesting.properties"/> 
+  <property file="${properties.dir}/compilepath.properties"/> 
+  
+<!-- Targets -->
+
+  <target name="memorytests" 
+          description="Build Derby memory tests">
+    <javac
+      source="1.4"
+      target="1.4"
+      bootclasspath="${empty}"
+      nowarn="on"
+      debug="true"
+      depend="${depend}"
+      deprecation="${deprecation}"
+      optimize="${optimize}"
+      proceed="${proceed}"
+      verbose="${verbose}" 
+      srcdir="${derby.testing.src.dir}"
+      destdir="${out.dir}">
+      <classpath>
+        <pathelement path="${compile.classpath}"/>
+        <pathelement path="${junit}"/>
+      </classpath>
+      <include name="${derby.testing.functest.dir}/tests/memory/*.java"/>
+    </javac>
+  </target>
+  
+</project>
+

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/memory/build.xml
------------------------------------------------------------------------------
    svn:eol-style = native