| /* |
| * Derby - Class org.apache.derbyTesting.functionTests.tests.memory.Derby5730Test |
| * |
| * 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.io.IOException; |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.DriverManager; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.BaseTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.SpawnedProcess; |
| |
| /** |
| * Regression test for DERBY-5730. Repeatedly boot and shut down a database. |
| * In between call a function in the SYSFUN schema, a different one each time. |
| * Without the fix, a reference to the currently running database instance will |
| * be leaked for each iteration, and eventually an OutOfMemoryError is raised. |
| */ |
| public class Derby5730Test extends BaseTestCase { |
| |
| public Derby5730Test(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| // The test case uses DriverManager, so require JDBC 3.0 or higher. |
| if (JDBC.vmSupportsJDBC3()) { |
| return new BaseTestSuite(Derby5730Test.class); |
| } |
| return new BaseTestSuite("Derby5730Test - skipped"); |
| } |
| |
| /** |
| * Test case for DERBY-5730. The memory leak is only reproduced if the |
| * test case runs with capped heap size and the SYSFUN functions have not |
| * been called previously in the same JVM process. Spawn a new process to |
| * satisfy those requirements. |
| */ |
| public void testLeak() throws IOException { |
| String[] cmd = {"-Xmx16M", getClass().getName()}; |
| SpawnedProcess sp = new SpawnedProcess(execJavaCmd(cmd), "DERBY-5730"); |
| if (sp.complete() != 0) { |
| fail(sp.getFailMessage("Process failed")); |
| } |
| } |
| |
| private final static Integer ZERO = Integer.valueOf("0"); |
| private final static Integer ONE = Integer.valueOf("1"); |
| private final static Integer TWO = Integer.valueOf("2"); |
| |
| /** |
| * These are the functions in the SYSFUN schema. The second value in each |
| * row tells how many arguments the function takes. |
| */ |
| private final static Object[][] FUNCTIONS = { |
| {"ACOS", ONE}, |
| {"ASIN", ONE}, |
| {"ATAN", ONE}, |
| {"ATAN2", TWO}, |
| {"COS", ONE}, |
| {"SIN", ONE}, |
| {"TAN", ONE}, |
| {"PI", ZERO}, |
| {"DEGREES", ONE}, |
| {"RADIANS", ONE}, |
| {"LN", ONE}, |
| {"LOG", ONE}, |
| {"LOG10", ONE}, |
| {"EXP", ONE}, |
| {"CEIL", ONE}, |
| {"CEILING", ONE}, |
| {"FLOOR", ONE}, |
| {"SIGN", ONE}, |
| {"RANDOM", ZERO}, |
| {"RAND", ONE}, |
| {"COT", ONE}, |
| {"COSH", ONE}, |
| {"SINH", ONE}, |
| {"TANH", ONE}, |
| }; |
| |
| /** |
| * Boot and repeatedly reboot a database, calling SYSFUN functions in |
| * between. Eventually runs out of memory if DERBY-5730 is not fixed. |
| * Must run with capped memory size (-Xmx16M) to expose the memory leak. |
| */ |
| public static void main(String[] args) throws Exception { |
| Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); |
| for (int i = 0; i < FUNCTIONS.length; i++) { |
| Connection c = DriverManager.getConnection( |
| "jdbc:derby:memory:derby5730;create=true"); |
| prepareFunction(c, |
| (String) FUNCTIONS[i][0], |
| ((Integer) FUNCTIONS[i][1]).intValue()); |
| growDatabaseFootprint(c); |
| c.close(); |
| try { |
| DriverManager.getConnection( |
| "jdbc:derby:memory:derby5730;shutdown=true"); |
| fail("Shutdown should throw exception"); |
| } catch (SQLException sqle) { |
| BaseJDBCTestCase.assertSQLState("08006", sqle); |
| } |
| } |
| } |
| |
| /** |
| * Prepare a call to a function. Close the statement once it is prepared. |
| * Before the bug was fixed, preparing a call to a function in SYSFUN that |
| * hadn't been prepared in the same JVM process, would leak a reference to |
| * the current database instance. |
| */ |
| private static void prepareFunction(Connection c, String name, int args) |
| throws SQLException { |
| StringBuffer sql = new StringBuffer("VALUES "); |
| sql.append(name); |
| sql.append('('); |
| for (int i = 0; i < args; i++) { |
| if (i > 0) sql.append(','); |
| sql.append('?'); |
| } |
| sql.append(')'); |
| |
| String sqlText = sql.toString(); |
| |
| System.out.println(sqlText); |
| PreparedStatement ps = c.prepareStatement(sqlText); |
| ps.close(); |
| } |
| |
| /** |
| * Perform some database operations so that the internal structures of |
| * the database instance (caches, for example) are filled, and the memory |
| * footprint of the database instance grows. This is done to make the |
| * test case run out of memory faster. |
| */ |
| private static void growDatabaseFootprint(Connection c) |
| throws SQLException { |
| DatabaseMetaData dmd = c.getMetaData(); |
| JDBC.assertDrainResults(dmd.getColumns(null, "%", "%", "%")); |
| JDBC.assertDrainResults(dmd.getTables(null, "%", "%", null)); |
| } |
| } |