blob: 9cc47d670654e1381ca001a2b01491a1c53c4c73 [file] [log] [blame]
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* TestSuite for BasicDataSource with abandoned connection trace enabled
*
* @author Dirk Verbeeck
*/
public class TestAbandonedBasicDataSource extends TestBasicDataSource {
private StringWriter sw;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
// abandoned enabled but should not affect the basic tests
// (very high timeout)
ds.setLogAbandoned(true);
ds.setRemoveAbandonedOnBorrow(true);
ds.setRemoveAbandonedOnMaintenance(true);
ds.setRemoveAbandonedTimeout(10000);
sw = new StringWriter();
ds.setAbandonedLogWriter(new PrintWriter(sw));
}
// ---------- Abandoned Test -----------
@Test
public void testAbandoned() throws Exception {
// force abandoned
ds.setRemoveAbandonedTimeout(0);
ds.setMaxTotal(1);
for (int i = 0; i < 3; i++) {
assertNotNull(ds.getConnection());
}
}
@Test
public void testAbandonedClose() throws Exception {
// force abandoned
ds.setRemoveAbandonedTimeout(0);
ds.setMaxTotal(1);
ds.setAccessToUnderlyingConnectionAllowed(true);
final Connection conn1 = getConnection();
assertNotNull(conn1);
assertEquals(1, ds.getNumActive());
final Connection conn2 = getConnection();
// Attempt to borrow object triggers abandoned cleanup
// conn1 should be closed by the pool to make room
assertNotNull(conn2);
assertEquals(1, ds.getNumActive());
// Verify that conn1 is closed
assertTrue(((DelegatingConnection<?>) conn1).getInnermostDelegate().isClosed());
conn2.close();
assertEquals(0, ds.getNumActive());
// Second close on conn1 is OK as of dbcp 1.3
conn1.close();
assertEquals(0, ds.getNumActive());
final String string = sw.toString();
assertTrue(string, string.contains("testAbandonedClose"));
}
@Test
public void testAbandonedCloseWithExceptions() throws Exception {
// force abandoned
ds.setRemoveAbandonedTimeout(0);
ds.setMaxTotal(1);
ds.setAccessToUnderlyingConnectionAllowed(true);
final Connection conn1 = getConnection();
assertNotNull(conn1);
assertEquals(1, ds.getNumActive());
final Connection conn2 = getConnection();
assertNotNull(conn2);
assertEquals(1, ds.getNumActive());
// set an IO failure causing the isClosed method to fail
final TesterConnection tconn1 = (TesterConnection) ((DelegatingConnection<?>) conn1).getInnermostDelegate();
tconn1.setFailure(new IOException("network error"));
final TesterConnection tconn2 = (TesterConnection) ((DelegatingConnection<?>) conn2).getInnermostDelegate();
tconn2.setFailure(new IOException("network error"));
try {
conn2.close();
} catch (final SQLException ex) {
/* Ignore */
}
assertEquals(0, ds.getNumActive());
try {
conn1.close();
} catch (final SQLException ex) {
}
assertEquals(0, ds.getNumActive());
final String string = sw.toString();
assertTrue(string, string.contains("testAbandonedCloseWithExceptions"));
}
/**
* Verify that lastUsed property is updated when a connection
* creates or prepares a statement
*/
@Test
public void testLastUsed() throws Exception {
ds.setRemoveAbandonedTimeout(1);
ds.setMaxTotal(2);
try (Connection conn1 = ds.getConnection()) {
Thread.sleep(500);
try (Statement s = conn1.createStatement()) {} // Should reset lastUsed
Thread.sleep(800);
final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
try (Statement s = conn1.createStatement()) {} // Should still be OK
conn2.close();
Thread.sleep(500);
try (PreparedStatement ps = conn1.prepareStatement("SELECT 1 FROM DUAL")) {} // reset
Thread.sleep(800);
try (Connection c = ds.getConnection()) {} // trigger abandoned cleanup again
try (Statement s = conn1.createStatement()) {}
}
}
/**
* Verify that lastUsed property is updated when a connection
* prepares a callable statement.
*/
@Test
public void testLastUsedPrepareCall() throws Exception {
ds.setRemoveAbandonedTimeout(1);
ds.setMaxTotal(2);
try (Connection conn1 = ds.getConnection()) {
Thread.sleep(500);
try (CallableStatement cs = conn1.prepareCall("{call home}")) {} // Should reset lastUsed
Thread.sleep(800);
final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
try (CallableStatement cs = conn1.prepareCall("{call home}")) {} // Should still be OK
conn2.close();
Thread.sleep(500);
try (CallableStatement cs = conn1.prepareCall("{call home}")) {} // reset
Thread.sleep(800);
try (Connection c = ds.getConnection()) {} // trigger abandoned cleanup again
try (Statement s = conn1.createStatement()) {}
}
}
/**
* DBCP-343 - verify that using a DelegatingStatement updates
* the lastUsed on the parent connection
*/
@Test
public void testLastUsedPreparedStatementUse() throws Exception {
ds.setRemoveAbandonedTimeout(1);
ds.setMaxTotal(2);
try (Connection conn1 = ds.getConnection();
Statement st = conn1.createStatement()) {
final String querySQL = "SELECT 1 FROM DUAL";
Thread.sleep(500);
Assert.assertNotNull(st.executeQuery(querySQL)); // Should reset lastUsed
Thread.sleep(800);
final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
Assert.assertNotNull(st.executeQuery(querySQL)); // Should still be OK
conn2.close();
Thread.sleep(500);
st.executeUpdate(""); // Should also reset
Thread.sleep(800);
try (Connection c = ds.getConnection()) {} // trigger abandoned cleanup again
try (Statement s = conn1.createStatement()) {} // Connection should still be good
}
}
/**
* DBCP-343 - verify additional operations reset lastUsed on
* the parent connection
*/
@Test
public void testLastUsedUpdate() throws Exception {
final DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection();
final PreparedStatement ps = conn.prepareStatement("");
final CallableStatement cs = conn.prepareCall("");
final Statement st = conn.prepareStatement("");
checkLastUsedStatement(ps, conn);
checkLastUsedPreparedStatement(ps, conn);
checkLastUsedStatement(cs, conn);
checkLastUsedPreparedStatement(cs, conn);
checkLastUsedStatement(st, conn);
}
/**
* DBCP-180 - verify that a GC can clean up an unused Statement when it is
* no longer referenced even when it is tracked via the AbandonedTrace
* mechanism.
*/
@Test
public void testGarbageCollectorCleanUp01() throws Exception {
final DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection();
Assert.assertEquals(0, conn.getTrace().size());
createStatement(conn);
Assert.assertEquals(1, conn.getTrace().size());
System.gc();
Assert.assertEquals(0, conn.getTrace().size());
}
/**
* DBCP-180 - things get more interesting with statement pooling.
*/
@Test
public void testGarbageCollectorCleanUp02() throws Exception {
ds.setPoolPreparedStatements(true);
ds.setAccessToUnderlyingConnectionAllowed(true);
final DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection();
final PoolableConnection poolableConn = (PoolableConnection) conn.getDelegate();
final PoolingConnection poolingConn = (PoolingConnection) poolableConn.getDelegate();
@SuppressWarnings("unchecked")
final
GenericKeyedObjectPool<PStmtKey,DelegatingPreparedStatement> gkop =
(GenericKeyedObjectPool<PStmtKey,DelegatingPreparedStatement>) TesterUtils.getField(poolingConn, "_pstmtPool");
Assert.assertEquals(0, conn.getTrace().size());
Assert.assertEquals(0, gkop.getNumActive());
createStatement(conn);
Assert.assertEquals(1, conn.getTrace().size());
Assert.assertEquals(1, gkop.getNumActive());
System.gc();
// Finalization happens in a separate thread. Give the test time for
// that to complete.
int count = 0;
while (count < 50 && gkop.getNumActive() > 0) {
Thread.sleep(100);
count++;
}
Assert.assertEquals(0, gkop.getNumActive());
Assert.assertEquals(0, conn.getTrace().size());
}
private void createStatement(final Connection conn) throws Exception{
final PreparedStatement ps = conn.prepareStatement("");
Assert.assertNotNull(ps);
}
/**
* Verifies that Statement executeXxx methods update lastUsed on the parent connection
*/
private void checkLastUsedStatement(final Statement st, final DelegatingConnection<?> conn) throws Exception {
st.execute("");
assertAndReset(conn);
st.execute("", new int[] {});
assertAndReset(conn);
st.execute("", 0);
assertAndReset(conn);
st.executeBatch();
assertAndReset(conn);
Assert.assertNotNull(st.executeQuery(""));
assertAndReset(conn);
st.executeUpdate("");
assertAndReset(conn);
st.executeUpdate("", new int[] {});
assertAndReset(conn);
st.executeUpdate("", 0);
assertAndReset(conn);
st.executeUpdate("", new String[] {});
assertAndReset(conn);
}
/**
* Verifies that PreparedStatement executeXxx methods update lastUsed on the parent connection
*/
private void checkLastUsedPreparedStatement(final PreparedStatement ps, final DelegatingConnection<?> conn) throws Exception {
ps.execute();
assertAndReset(conn);
Assert.assertNotNull(ps.executeQuery());
assertAndReset(conn);
ps.executeUpdate();
assertAndReset(conn);
}
/**
* Verifies that con.lastUsed has been updated and then resets it to 0
*/
private void assertAndReset(final DelegatingConnection<?> con) {
assertTrue(con.getLastUsed() > 0);
con.setLastUsed(0);
}
}