/* * Copyright (C) 2013 Brett Wooldridge * * 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.zaxxer.hikari.pool; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.Level; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.mocks.StubDataSource; import com.zaxxer.hikari.mocks.StubStatement; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; import com.zaxxer.hikari.util.UtilityElf; /** * @author Brett Wooldridge */ public class TestConnections { @Before public void before() { TestElf.setSlf4jTargetStream(HikariPool.class, System.err); TestElf.setSlf4jLogLevel(HikariPool.class, Level.DEBUG); TestElf.setSlf4jLogLevel(PoolBase.class, Level.DEBUG); } @After public void after() { System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); TestElf.setSlf4jLogLevel(HikariPool.class, Level.WARN); TestElf.setSlf4jLogLevel(PoolBase.class, Level.WARN); } @Test public void testCreate() throws SQLException { HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setConnectionInitSql("SELECT 1"); config.setReadOnly(true); config.setConnectionTimeout(2500); config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setLoginTimeout(10); Assert.assertSame(10, ds.getLoginTimeout()); HikariPool pool = TestElf.getPool(ds); ds.getConnection().close(); Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); Connection connection = ds.getConnection(); Assert.assertNotNull(connection); Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?"); Assert.assertNotNull(statement); statement.setInt(1, 0); ResultSet resultSet = statement.executeQuery(); Assert.assertNotNull(resultSet); Assert.assertFalse(resultSet.next()); resultSet.close(); statement.close(); connection.close(); Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); } } @Test public void testMaxLifetime() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailFast(false); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); TestElf.setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); ds.setMaxLifetime(700); HikariPool pool = TestElf.getPool(ds); Assert.assertSame("Total connections not as expected", 0, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); Connection connection = ds.getConnection(); Connection unwrap = connection.unwrap(Connection.class); Assert.assertNotNull(connection); Assert.assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); connection.close(); Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); Connection connection2 = ds.getConnection(); Connection unwrap2 = connection2.unwrap(Connection.class); Assert.assertSame(unwrap, unwrap2); Assert.assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); connection2.close(); quietlySleep(TimeUnit.SECONDS.toMillis(2)); connection2 = ds.getConnection(); Assert.assertNotSame("Expected a different connection", connection, connection2); connection2.close(); Assert.assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); } finally { TestElf.setConfigUnitTest(false); } } @Test public void testMaxLifetime2() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); TestElf.setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setMaxLifetime(700); HikariPool pool = TestElf.getPool(ds); Assert.assertSame("Total connections not as expected", 0, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); Connection connection = ds.getConnection(); Connection unwrap = connection.unwrap(Connection.class); Assert.assertNotNull(connection); Assert.assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); connection.close(); Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); Connection connection2 = ds.getConnection(); Connection unwrap2 = connection2.unwrap(Connection.class); Assert.assertSame(unwrap, unwrap2); Assert.assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); connection2.close(); quietlySleep(800); connection2 = ds.getConnection(); Assert.assertNotSame("Expected a different connection", connection, connection2); connection2.close(); Assert.assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); } finally { TestElf.setConfigUnitTest(false); } } @Test public void testDoubleClose() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection connection = ds.getConnection(); connection.close(); // should no-op connection.abort(null); Assert.assertTrue("Connection should have closed", connection.isClosed()); Assert.assertFalse("Connection should have closed", connection.isValid(5)); Assert.assertTrue("Expected to contain ClosedConnection, but was " + connection, connection.toString().contains("ClosedConnection")); connection.close(); } } @Test public void testEviction() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(5); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection connection = ds.getConnection(); HikariPool pool = TestElf.getPool(ds); Assert.assertEquals(1, pool.getTotalConnections()); ds.evictConnection(connection); Assert.assertEquals(0, pool.getTotalConnections()); } } @Test public void testBackfill() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(4); config.setConnectionTimeout(1000); config.setInitializationFailFast(false); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = TestElf.getPool(ds); UtilityElf.quietlySleep(500); Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); // This will take the pool down to zero Connection connection = ds.getConnection(); Assert.assertNotNull(connection); Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); Assert.assertNotNull(statement); ResultSet resultSet = statement.executeQuery(); Assert.assertNotNull(resultSet); try { statement.getMaxFieldSize(); Assert.fail(); } catch (Exception e) { Assert.assertSame(SQLException.class, e.getClass()); } pool.logPoolState("testBackfill() before close..."); // The connection will be ejected from the pool here connection.close(); UtilityElf.quietlySleep(500); pool.logPoolState("testBackfill() after close..."); Assert.assertSame("Total connections not as expected", 0, pool.getTotalConnections()); Assert.assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); // This will cause a backfill connection = ds.getConnection(); connection.close(); Assert.assertTrue("Total connections not as expected", pool.getTotalConnections() > 0); Assert.assertTrue("Idle connections not as expected", pool.getIdleConnections() > 0); } } @Test public void testMaximumPoolLimit() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(4); config.setConnectionTimeout(20000); config.setInitializationFailFast(true); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); final AtomicReference<Exception> ref = new AtomicReference<>(); StubConnection.count.set(0); // reset counter try (final HikariDataSource ds = new HikariDataSource(config)) { final HikariPool pool = TestElf.getPool(ds); Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { try { pool.logPoolState("Before acquire "); Connection connection = ds.getConnection(); pool.logPoolState("After acquire "); quietlySleep(500); connection.close(); } catch (Exception e) { ref.set(e); } } }); } for (int i = 0; i < threads.length; i++) { threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } pool.logPoolState("before check "); Assert.assertNull((ref.get() != null ? ref.get().toString() : ""), ref.get()); Assert.assertSame("StubConnection count not as expected", 4+1, StubConnection.count.get()); // 1st connection is in pool.initializeConnections() } } @Test public void testOldDriver() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.oldDriver = true; StubStatement.oldDriver = true; try (HikariDataSource ds = new HikariDataSource(config)) { quietlySleep(500); Connection connection = ds.getConnection(); connection.close(); quietlySleep(500); connection = ds.getConnection(); } finally { StubConnection.oldDriver = false; StubStatement.oldDriver = false; } } @Test public void testSuspendResume() throws Exception { HikariConfig config = new HikariConfig(); config.setMinimumIdle(3); config.setMaximumPoolSize(3); config.setConnectionTimeout(2500); config.setAllowPoolSuspension(true); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (final HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = TestElf.getPool(ds); while (pool.getTotalConnections() < 3) { quietlySleep(50); } Thread t = new Thread(new Runnable() { @Override public void run() { try { ds.getConnection(); ds.getConnection(); } catch (Exception e) { Assert.fail(); } } }); Connection c3 = ds.getConnection(); Assert.assertEquals(2, pool.getIdleConnections()); pool.suspendPool(); t.start(); quietlySleep(500); Assert.assertEquals(2, pool.getIdleConnections()); c3.close(); Assert.assertEquals(3, pool.getIdleConnections()); pool.resumePool(); quietlySleep(500); Assert.assertEquals(1, pool.getIdleConnections()); } } @Test public void testInitializationFailure() throws SQLException { StubDataSource stubDataSource = new StubDataSource(); stubDataSource.setThrowException(new SQLException("Connection refused")); try (HikariDataSource ds = new HikariDataSource()) { ds.setMinimumIdle(3); ds.setMaximumPoolSize(3); ds.setConnectionTimeout(2500); ds.setAllowPoolSuspension(true); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSource(stubDataSource); try (Connection c = ds.getConnection()) { Assert.fail("Initialization should have failed"); } catch (PoolInitializationException e) { // passed } } } @Test public void testInvalidConnectionTestQuery() { class BadConnection extends StubConnection { /** {@inheritDoc} */ @Override public Statement createStatement() throws SQLException { throw new SQLException("Bad query or something."); } } StubDataSource stubDataSource = new StubDataSource() { /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { return new BadConnection(); } }; HikariConfig config = new HikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailFast(false); config.setDataSource(stubDataSource); try (HikariDataSource ds = new HikariDataSource(config); Connection c = ds.getConnection()) { Assert.fail("getConnection() should have failed"); } catch (SQLException e) { Assert.assertSame("Bad query or something.", e.getNextException().getMessage()); } } }