| /* |
| * 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.commons.dbcp2; |
| |
| import java.sql.CallableStatement; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.Hashtable; |
| import java.util.Random; |
| import java.util.Stack; |
| |
| import org.junit.After; |
| import org.junit.Test; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| // XXX FIX ME XXX |
| // this class still needs some cleanup, but at least |
| // this consolidates most of the relevant test code |
| // in a fairly re-usable fashion |
| // XXX FIX ME XXX |
| |
| /** |
| * Base test suite for DBCP pools. |
| * |
| * @author Rodney Waldhoff |
| * @author Sean C. Sullivan |
| * @author John McNally |
| * @author Dirk Verbeeck |
| */ |
| public abstract class TestConnectionPool { |
| |
| @After |
| public void tearDown() throws Exception { |
| // Close any connections opened by the test |
| while (!connections.isEmpty()) { |
| Connection conn = connections.pop(); |
| try { |
| conn.close(); |
| } catch (final Exception ex) { |
| // ignore |
| } finally { |
| conn = null; |
| } |
| } |
| } |
| |
| protected abstract Connection getConnection() throws Exception; |
| |
| protected int getMaxTotal() { |
| return 10; |
| } |
| |
| protected long getMaxWaitMillis() { |
| return 100L; |
| } |
| |
| /** Connections opened during the course of a test */ |
| protected Stack<Connection> connections = new Stack<>(); |
| |
| /** Acquire a connection and push it onto the connections stack */ |
| protected Connection newConnection() throws Exception { |
| final Connection connection = getConnection(); |
| connections.push(connection); |
| return connection; |
| } |
| |
| // ----------- Utility Methods --------------------------------- |
| |
| protected String getUsername(final Connection conn) throws SQLException { |
| final Statement stmt = conn.createStatement(); |
| final ResultSet rs = stmt.executeQuery("select username"); |
| if (rs.next()) { |
| return rs.getString(1); |
| } |
| return null; |
| } |
| |
| // ----------- tests --------------------------------- |
| |
| @Test |
| public void testClearWarnings() throws Exception { |
| final Connection[] c = new Connection[getMaxTotal()]; |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| assertTrue(c[i] != null); |
| |
| // generate SQLWarning on connection |
| try (CallableStatement cs = c[i].prepareCall("warning")){ |
| } |
| } |
| |
| for (final Connection element : c) { |
| assertNotNull(element.getWarnings()); |
| } |
| |
| for (final Connection element : c) { |
| element.close(); |
| } |
| |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| } |
| |
| for (final Connection element : c) { |
| // warnings should have been cleared by putting the connection back in the pool |
| assertNull(element.getWarnings()); |
| } |
| |
| for (final Connection element : c) { |
| element.close(); |
| } |
| } |
| |
| @Test |
| public void testIsClosed() throws Exception { |
| for(int i=0;i<getMaxTotal();i++) { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| conn.close(); |
| assertTrue(conn.isClosed()); |
| } |
| } |
| |
| /** |
| * Verify the close method can be called multiple times on a single connection without |
| * an exception being thrown. |
| */ |
| @Test |
| public void testCanCloseConnectionTwice() throws Exception { |
| for (int i = 0; i < getMaxTotal(); i++) { // loop to show we *can* close again once we've borrowed it from the pool again |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| conn.close(); |
| assertTrue(conn.isClosed()); |
| conn.close(); |
| assertTrue(conn.isClosed()); |
| } |
| } |
| |
| @Test |
| public void testCanCloseStatementTwice() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| for(int i=0;i<2;i++) { // loop to show we *can* close again once we've borrowed it from the pool again |
| final Statement stmt = conn.createStatement(); |
| assertNotNull(stmt); |
| assertFalse(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testCanClosePreparedStatementTwice() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| for(int i=0;i<2;i++) { // loop to show we *can* close again once we've borrowed it from the pool again |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| assertFalse(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testCanCloseCallableStatementTwice() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| for(int i=0;i<2;i++) { // loop to show we *can* close again once we've borrowed it from the pool again |
| final PreparedStatement stmt = conn.prepareCall("select * from dual"); |
| assertNotNull(stmt); |
| assertFalse(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| stmt.close(); |
| assertTrue(isClosed(stmt)); |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testCanCloseResultSetTwice() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| assertTrue(!conn.isClosed()); |
| for(int i=0;i<2;i++) { // loop to show we *can* close again once we've borrowed it from the pool again |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertFalse(isClosed(rset)); |
| rset.close(); |
| assertTrue(isClosed(rset)); |
| rset.close(); |
| assertTrue(isClosed(rset)); |
| rset.close(); |
| assertTrue(isClosed(rset)); |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testBackPointers() throws Exception { |
| // normal statement |
| Connection conn = newConnection(); |
| assertBackPointers(conn, conn.createStatement()); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.createStatement(0, 0)); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.createStatement(0, 0, 0)); |
| |
| // prepared statement |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual")); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual", 0)); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual", 0, 0)); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual", 0, 0, 0)); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual", new int[0])); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareStatement("select * from dual", new String[0])); |
| |
| // callable statement |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareCall("select * from dual")); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareCall("select * from dual", 0, 0)); |
| conn = newConnection(); |
| assertBackPointers(conn, conn.prepareCall("select * from dual", 0, 0, 0)); |
| } |
| |
| protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException { |
| assertFalse(conn.isClosed()); |
| assertFalse(isClosed(statement)); |
| |
| assertSame("statement.getConnection() should return the exact same connection instance that was used to create the statement", |
| conn, statement.getConnection()); |
| |
| final ResultSet resultSet = statement.getResultSet(); |
| assertFalse(isClosed(resultSet)); |
| assertSame("resultSet.getStatement() should return the exact same statement instance that was used to create the result set", |
| statement, resultSet.getStatement()); |
| |
| final ResultSet executeResultSet = statement.executeQuery("select * from dual"); |
| assertFalse(isClosed(executeResultSet)); |
| assertSame("resultSet.getStatement() should return the exact same statement instance that was used to create the result set", |
| statement, executeResultSet.getStatement()); |
| |
| final ResultSet keysResultSet = statement.getGeneratedKeys(); |
| assertFalse(isClosed(keysResultSet)); |
| assertSame("resultSet.getStatement() should return the exact same statement instance that was used to create the result set", |
| statement, keysResultSet.getStatement()); |
| |
| ResultSet preparedResultSet = null; |
| if (statement instanceof PreparedStatement) { |
| final PreparedStatement preparedStatement = (PreparedStatement) statement; |
| preparedResultSet = preparedStatement.executeQuery(); |
| assertFalse(isClosed(preparedResultSet)); |
| assertSame("resultSet.getStatement() should return the exact same statement instance that was used to create the result set", |
| statement, preparedResultSet.getStatement()); |
| } |
| |
| |
| resultSet.getStatement().getConnection().close(); |
| assertTrue(conn.isClosed()); |
| assertTrue(isClosed(statement)); |
| assertTrue(isClosed(resultSet)); |
| assertTrue(isClosed(executeResultSet)); |
| assertTrue(isClosed(keysResultSet)); |
| if (preparedResultSet != null) { |
| assertTrue(isClosed(preparedResultSet)); |
| } |
| } |
| |
| @Test |
| public void testSimple() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| conn.close(); |
| } |
| |
| @Test |
| public void testRepeatedBorrowAndReturn() throws Exception { |
| for(int i=0;i<100;i++) { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testSimple2() throws Exception { |
| Connection conn = newConnection(); |
| assertNotNull(conn); |
| { |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| } |
| { |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| } |
| conn.close(); |
| try (Statement s = conn.createStatement()){ |
| fail("Can't use closed connections"); |
| } catch(final SQLException e) { |
| // expected |
| } |
| |
| conn = newConnection(); |
| assertNotNull(conn); |
| { |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| } |
| { |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| rset.close(); |
| stmt.close(); |
| } |
| conn.close(); |
| conn = null; |
| } |
| |
| @Test |
| public void testPooling() throws Exception { |
| // Grab a maximal set of open connections from the pool |
| final Connection[] c = new Connection[getMaxTotal()]; |
| final Connection[] u = new Connection[getMaxTotal()]; |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| if (c[i] instanceof DelegatingConnection) { |
| u[i] = ((DelegatingConnection<?>) c[i]).getInnermostDelegate(); |
| } else { |
| for (int j = 0; j <= i; j++) { |
| c[j].close(); |
| } |
| return; // skip this test |
| } |
| } |
| // Close connections one at a time and get new ones, making sure |
| // the new ones come from the pool |
| for (final Connection element : c) { |
| element.close(); |
| final Connection con = newConnection(); |
| final Connection underCon = |
| ((DelegatingConnection<?>) con).getInnermostDelegate(); |
| assertTrue("Failed to get connection", underCon != null); |
| boolean found = false; |
| for (int j = 0; j < c.length; j++) { |
| if (underCon == u[j]) { |
| found = true; |
| break; |
| } |
| } |
| assertTrue("New connection not from pool", found); |
| con.close(); |
| } |
| } |
| |
| @Test |
| public void testAutoCommitBehavior() throws Exception { |
| final Connection conn0 = newConnection(); |
| assertNotNull("connection should not be null", conn0); |
| assertTrue("autocommit should be true for conn0", conn0.getAutoCommit()); |
| |
| final Connection conn1 = newConnection(); |
| assertTrue("autocommit should be true for conn1", conn1.getAutoCommit() ); |
| conn1.close(); |
| |
| assertTrue("autocommit should be true for conn0", conn0.getAutoCommit()); |
| conn0.setAutoCommit(false); |
| assertFalse("autocommit should be false for conn0", conn0.getAutoCommit()); |
| conn0.close(); |
| |
| final Connection conn2 = newConnection(); |
| assertTrue("autocommit should be true for conn2", conn2.getAutoCommit() ); |
| |
| final Connection conn3 = newConnection(); |
| assertTrue("autocommit should be true for conn3", conn3.getAutoCommit() ); |
| |
| conn2.close(); |
| |
| conn3.close(); |
| } |
| |
| /** "http://issues.apache.org/bugzilla/show_bug.cgi?id=12400" */ |
| @Test |
| public void testConnectionsAreDistinct() throws Exception { |
| final Connection[] conn = new Connection[getMaxTotal()]; |
| for(int i=0;i<conn.length;i++) { |
| conn[i] = newConnection(); |
| for(int j=0;j<i;j++) { |
| assertTrue(conn[j] != conn[i]); |
| assertTrue(!conn[j].equals(conn[i])); |
| } |
| } |
| for (final Connection element : conn) { |
| element.close(); |
| } |
| } |
| |
| @Test |
| public void testOpening() throws Exception { |
| final Connection[] c = new Connection[getMaxTotal()]; |
| // test that opening new connections is not closing previous |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| assertTrue(c[i] != null); |
| for (int j = 0; j <= i; j++) { |
| assertTrue(!c[j].isClosed()); |
| } |
| } |
| |
| for (final Connection element : c) { |
| element.close(); |
| } |
| } |
| |
| @Test |
| public void testClosing() throws Exception { |
| final Connection[] c = new Connection[getMaxTotal()]; |
| // open the maximum connections |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| } |
| |
| // close one of the connections |
| c[0].close(); |
| assertTrue(c[0].isClosed()); |
| |
| // get a new connection |
| c[0] = newConnection(); |
| |
| for (final Connection element : c) { |
| element.close(); |
| } |
| } |
| |
| @Test |
| public void testMaxTotal() throws Exception { |
| final Connection[] c = new Connection[getMaxTotal()]; |
| for (int i = 0; i < c.length; i++) { |
| c[i] = newConnection(); |
| assertTrue(c[i] != null); |
| } |
| |
| try { |
| newConnection(); |
| fail("Allowed to open more than DefaultMaxTotal connections."); |
| } catch (final java.sql.SQLException e) { |
| // should only be able to open 10 connections, so this test should |
| // throw an exception |
| } |
| |
| for (final Connection element : c) { |
| element.close(); |
| } |
| } |
| |
| /** |
| * DBCP-128: BasicDataSource.getConnection() |
| * Connections don't work as hashtable keys |
| */ |
| @Test |
| public void testHashing() throws Exception { |
| final Connection con = getConnection(); |
| final Hashtable<Connection, String> hash = new Hashtable<>(); |
| hash.put(con, "test"); |
| assertEquals("test", hash.get(con)); |
| assertTrue(hash.containsKey(con)); |
| assertTrue(hash.contains("test")); |
| hash.clear(); |
| con.close(); |
| } |
| |
| @Test |
| public void testThreaded() { |
| final TestThread[] threads = new TestThread[getMaxTotal()]; |
| for(int i=0;i<threads.length;i++) { |
| threads[i] = new TestThread(50,50); |
| final Thread t = new Thread(threads[i]); |
| t.start(); |
| } |
| for(int i=0;i<threads.length;i++) { |
| while(!(threads[i]).complete()) { |
| try { |
| Thread.sleep(100L); |
| } catch(final Exception e) { |
| // ignored |
| } |
| } |
| if(threads[i] != null && threads[i].failed()) { |
| fail("Thread failed: " + i); |
| } |
| } |
| } |
| |
| class TestThread implements Runnable { |
| java.util.Random _random = new java.util.Random(); |
| boolean _complete = false; |
| boolean _failed = false; |
| int _iter = 100; |
| int _delay = 50; |
| |
| public TestThread() { |
| } |
| |
| public TestThread(final int iter) { |
| _iter = iter; |
| } |
| |
| public TestThread(final int iter, final int delay) { |
| _iter = iter; |
| _delay = delay; |
| } |
| |
| public boolean complete() { |
| return _complete; |
| } |
| |
| public boolean failed() { |
| return _failed; |
| } |
| |
| @Override |
| public void run() { |
| for(int i=0;i<_iter;i++) { |
| try { |
| Thread.sleep(_random.nextInt(_delay)); |
| } catch(final Exception e) { |
| // ignored |
| } |
| try (Connection conn = newConnection(); |
| PreparedStatement stmt = conn.prepareStatement( |
| "select 'literal', SYSDATE from dual"); |
| ResultSet rset = stmt.executeQuery();) { |
| try { |
| Thread.sleep(_random.nextInt(_delay)); |
| } catch(final Exception e) { |
| // ignored |
| } |
| } catch(final Exception e) { |
| e.printStackTrace(); |
| _failed = true; |
| _complete = true; |
| break; |
| } |
| } |
| _complete = true; |
| } |
| } |
| |
| // Bugzilla Bug 24328: PooledConnectionImpl ignores resultsetType |
| // and Concurrency if statement pooling is not enabled |
| // http://issues.apache.org/bugzilla/show_bug.cgi?id=24328 |
| @Test |
| public void testPrepareStatementOptions() throws Exception |
| { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| final PreparedStatement stmt = conn.prepareStatement("select * from dual", |
| ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| |
| assertEquals(ResultSet.TYPE_SCROLL_SENSITIVE, rset.getType()); |
| assertEquals(ResultSet.CONCUR_UPDATABLE, rset.getConcurrency()); |
| |
| rset.close(); |
| stmt.close(); |
| conn.close(); |
| } |
| |
| // Bugzilla Bug 24966: NullPointer with Oracle 9 driver |
| // wrong order of passivate/close when a rset isn't closed |
| @Test |
| public void testNoRsetClose() throws Exception { |
| final Connection conn = newConnection(); |
| assertNotNull(conn); |
| final PreparedStatement stmt = conn.prepareStatement("test"); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.getResultSet(); |
| assertNotNull(rset); |
| // forget to close the resultset: rset.close(); |
| stmt.close(); |
| conn.close(); |
| } |
| |
| // Bugzilla Bug 26966: Connectionpool's connections always returns same |
| @Test |
| public void testHashCode() throws Exception { |
| final Connection conn1 = newConnection(); |
| assertNotNull(conn1); |
| final Connection conn2 = newConnection(); |
| assertNotNull(conn2); |
| |
| assertTrue(conn1.hashCode() != conn2.hashCode()); |
| } |
| |
| protected boolean isClosed(final Statement statement) { |
| try { |
| statement.getWarnings(); |
| return false; |
| } catch (final SQLException e) { |
| // getWarnings throws an exception if the statement is |
| // closed, but could throw an exception for other reasons |
| // in this case it is good enough to assume the statement |
| // is closed |
| return true; |
| } |
| } |
| |
| protected boolean isClosed(final ResultSet resultSet) { |
| try { |
| resultSet.getWarnings(); |
| return false; |
| } catch (final SQLException e) { |
| // getWarnings throws an exception if the statement is |
| // closed, but could throw an exception for other reasons |
| // in this case it is good enough to assume the result set |
| // is closed |
| return true; |
| } |
| } |
| |
| private static final boolean DISPLAY_THREAD_DETAILS= |
| Boolean.valueOf(System.getProperty("TestConnectionPool.display.thread.details", "false")).booleanValue(); |
| // To pass this to a Maven test, use: |
| // mvn test -DargLine="-DTestConnectionPool.display.thread.details=true" |
| // @see http://jira.codehaus.org/browse/SUREFIRE-121 |
| |
| /** |
| * Launches a group of 2 * getMaxTotal() threads, each of which will attempt to obtain a connection |
| * from the pool, hold it for {@code holdTime} ms, and then return it to the pool. If {@code loopOnce} is false, |
| * threads will continue this process indefinitely. If {@code expectError} is true, exactly 1/2 of the |
| * threads are expected to either throw exceptions or fail to complete. If {@code expectError} is false, |
| * all threads are expected to complete successfully. |
| * |
| * @param holdTime time in ms that a thread holds a connection before returning it to the pool |
| * @param expectError whether or not an error is expected |
| * @param loopOnce whether threads should complete the borrow - hold - return cycle only once, or loop indefinitely |
| * @param maxWaitMillis passed in by client - has no impact on the test itself, but does get reported |
| * |
| * @throws Exception |
| */ |
| protected void multipleThreads(final int holdTime, |
| final boolean expectError, final boolean loopOnce, |
| final long maxWaitMillis) throws Exception { |
| multipleThreads(holdTime, expectError, loopOnce, maxWaitMillis, 1, 2 * getMaxTotal(), 300); |
| } |
| |
| /** |
| * Launches a group of {@code numThreads} threads, each of which will attempt to obtain a connection |
| * from the pool, hold it for {@code holdTime} ms, and then return it to the pool. If {@code loopOnce} is false, |
| * threads will continue this process indefinitely. If {@code expectError} is true, exactly 1/2 of the |
| * threads are expected to either throw exceptions or fail to complete. If {@code expectError} is false, |
| * all threads are expected to complete successfully. Threads are stopped after {@code duration} ms. |
| * |
| * @param holdTime time in ms that a thread holds a connection before returning it to the pool |
| * @param expectError whether or not an error is expected |
| * @param loopOnce whether threads should complete the borrow - hold - return cycle only once, or loop indefinitely |
| * @param maxWaitMillis passed in by client - has no impact on the test itself, but does get reported |
| * @param numThreads the number of threads |
| * @param duration duration in ms of test |
| * |
| * @throws Exception |
| */ |
| protected void multipleThreads(final int holdTime, |
| final boolean expectError, final boolean loopOnce, |
| final long maxWaitMillis, final int numStatements, final int numThreads, final long duration) throws Exception { |
| final long startTime = timeStamp(); |
| final PoolTest[] pts = new PoolTest[numThreads]; |
| // Catch Exception so we can stop all threads if one fails |
| final ThreadGroup threadGroup = new ThreadGroup("foo") { |
| @Override |
| public void uncaughtException(final Thread t, final Throwable e) { |
| for (final PoolTest pt : pts) { |
| pt.stop(); |
| } |
| } |
| }; |
| // Create all the threads |
| for (int i = 0; i < pts.length; i++) { |
| pts[i] = new PoolTest(threadGroup, holdTime, expectError, loopOnce, numStatements); |
| } |
| // Start all the threads |
| for (final PoolTest pt : pts) { |
| pt.start(); |
| } |
| |
| // Give all threads a chance to start and succeed |
| Thread.sleep(duration); |
| |
| // Stop threads |
| for (final PoolTest pt : pts) { |
| pt.stop(); |
| } |
| |
| /* |
| * Wait for all threads to terminate. |
| * This is essential to ensure that all threads have a chance to update success[0] |
| * and to ensure that the variable is published correctly. |
| */ |
| int done=0; |
| int failed=0; |
| int didNotRun = 0; |
| int loops=0; |
| for (final PoolTest poolTest : pts) { |
| poolTest.thread.join(); |
| loops += poolTest.loops; |
| final String state = poolTest.state; |
| if (DONE.equals(state)){ |
| done++; |
| } |
| if (poolTest.loops == 0){ |
| didNotRun++; |
| } |
| final Throwable thrown = poolTest.thrown; |
| if (thrown != null) { |
| failed++; |
| if (!expectError || !(thrown instanceof SQLException)){ |
| System.out.println("Unexpected error: "+thrown.getMessage()); |
| } |
| } |
| } |
| |
| final long time = timeStamp() - startTime; |
| System.out.println("Multithread test time = " + time |
| + " ms. Threads: " + pts.length |
| + ". Loops: " + loops |
| + ". Hold time: " + holdTime |
| + ". maxWaitMillis: " + maxWaitMillis |
| + ". Done: " + done |
| + ". Did not run: " + didNotRun |
| + ". Failed: " + failed |
| + ". expectError: " + expectError |
| ); |
| if (expectError) { |
| if (DISPLAY_THREAD_DETAILS || (pts.length/2 != failed)){ |
| final long offset = pts[0].created - 1000; // To reduce size of output numbers, but ensure they have 4 digits |
| System.out.println("Offset: "+offset); |
| for (int i = 0; i < pts.length; i++) { |
| final PoolTest pt = pts[i]; |
| System.out.println( |
| "Pre: " + (pt.preconnected-offset) // First, so can sort on this easily |
| + ". Post: " + (pt.postconnected != 0 ? Long.toString(pt.postconnected-offset): "-") |
| + ". Hash: " + pt.connHash |
| + ". Startup: " + (pt.started-pt.created) |
| + ". getConn(): " + (pt.connected != 0 ? Long.toString(pt.connected-pt.preconnected) : "-") |
| + ". Runtime: " + (pt.ended-pt.started) |
| + ". IDX: " + i |
| + ". Loops: " + pt.loops |
| + ". State: " + pt.state |
| + ". thrown: "+ pt.thrown |
| + "." |
| ); |
| } |
| } |
| if (didNotRun > 0){ |
| System.out.println("NOTE: some threads did not run the code: "+didNotRun); |
| } |
| // Perform initial sanity check: |
| assertTrue("Expected some of the threads to fail",failed > 0); |
| // Assume that threads that did not run would have timed out. |
| assertEquals("WARNING: Expected half the threads to fail",pts.length/2,failed+didNotRun); |
| } else { |
| assertEquals("Did not expect any threads to fail",0,failed); |
| } |
| } |
| private static int currentThreadCount = 0; |
| |
| private static final String DONE = "Done"; |
| |
| protected class PoolTest implements Runnable { |
| /** |
| * The number of milliseconds to hold onto a database connection |
| */ |
| private final int connHoldTime; |
| |
| private final int numStatements; |
| |
| private volatile boolean isRun; |
| |
| private String state; // No need to be volatile if it is read after the thread finishes |
| |
| private final Thread thread; |
| |
| private Throwable thrown; |
| |
| private final Random random = new Random(); |
| |
| // Debug for DBCP-318 |
| private final long created; // When object was created |
| private long started; // when thread started |
| private long ended; // when thread ended |
| private long preconnected; // just before connect |
| private long connected; // when thread last connected |
| private long postconnected; // when thread released connection |
| private int loops = 0; |
| private int connHash = 0; // Connection identity hashCode (to see which one is reused) |
| |
| private final boolean stopOnException; // If true, don't rethrow Exception |
| |
| private final boolean loopOnce; // If true, don't repeat loop |
| |
| public PoolTest(final ThreadGroup threadGroup, final int connHoldTime, final boolean isStopOnException) { |
| this(threadGroup, connHoldTime, isStopOnException, false, 1); |
| } |
| |
| public PoolTest(final ThreadGroup threadGroup, final int connHoldTime, final boolean isStopOnException, final int numStatements) { |
| this(threadGroup, connHoldTime, isStopOnException, false, numStatements); |
| } |
| |
| private PoolTest(final ThreadGroup threadGroup, final int connHoldTime, final boolean isStopOnException, final boolean once, final int numStatements) { |
| this.loopOnce = once; |
| this.connHoldTime = connHoldTime; |
| stopOnException = isStopOnException; |
| isRun = true; // Must be done here so main thread is guaranteed to be able to set it false |
| thrown = null; |
| thread = |
| new Thread(threadGroup, this, "Thread+" + currentThreadCount++); |
| thread.setDaemon(false); |
| created = timeStamp(); |
| this.numStatements = numStatements; |
| } |
| |
| public void start(){ |
| thread.start(); |
| } |
| |
| @Override |
| public void run() { |
| started = timeStamp(); |
| try { |
| while (isRun) { |
| loops++; |
| state = "Getting Connection"; |
| preconnected = timeStamp(); |
| final Connection conn = getConnection(); |
| connHash = System.identityHashCode(((DelegatingConnection<?>)conn).getInnermostDelegate()); |
| connected = timeStamp(); |
| state = "Using Connection"; |
| assertNotNull(conn); |
| final String sql = numStatements == 1 ? "select * from dual" : "select count " + random.nextInt(numStatements - 1); |
| final PreparedStatement stmt = |
| conn.prepareStatement(sql); |
| assertNotNull(stmt); |
| final ResultSet rset = stmt.executeQuery(); |
| assertNotNull(rset); |
| assertTrue(rset.next()); |
| state = "Holding Connection"; |
| Thread.sleep(connHoldTime); |
| state = "Closing ResultSet"; |
| rset.close(); |
| state = "Closing Statement"; |
| stmt.close(); |
| state = "Closing Connection"; |
| conn.close(); |
| postconnected = timeStamp(); |
| state = "Closed"; |
| if (loopOnce){ |
| break; // Or could set isRun=false |
| } |
| } |
| state = DONE; |
| } catch (final Throwable t) { |
| thrown = t; |
| if (!stopOnException) { |
| throw new RuntimeException(); |
| } |
| } finally { |
| ended = timeStamp(); |
| } |
| } |
| |
| public void stop() { |
| isRun = false; |
| } |
| |
| public Thread getThread() { |
| return thread; |
| } |
| } |
| |
| long timeStamp() { |
| return System.currentTimeMillis();// JVM 1.5+ System.nanoTime() / 1000000; |
| } |
| } |