package com.ankurm.hibernatedemo.hikari; import static org.assertj.core.api.Assertions.assertThat; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLTransientConnectionException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; /** * The failure mode a pool-sizing article should lead with: what actually happens when every * connection in the pool is checked out and something else asks for one more. Not "it blocks * forever" and not a generic timeout -- HikariCP throws a specific, named exception with the pool * name and the timeout value baked into the message. * *
Docs: docs/19-hikaricp-connection-pooling.md */ class HikariPoolExhaustionTest { private HikariDataSource dataSource; @AfterEach void tearDown() { if (dataSource != null) { dataSource.close(); } } @Test void requestingOneMoreConnectionThanMaximumPoolSize_failsFastWithSqlTransientConnectionException() throws Exception { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:h2:mem:hikariexhaustion;DB_CLOSE_DELAY=-1"); config.setUsername("sa"); config.setPoolName("exhaustion-pool"); config.setMaximumPoolSize(1); config.setConnectionTimeout(1000); // fail fast for the test, default is 30000ms dataSource = new HikariDataSource(config); // Hold the pool's one and only connection open. Connection held = dataSource.getConnection(); long start = System.currentTimeMillis(); Throwable thrown = catchThrowable(() -> dataSource.getConnection()); long waitedMs = System.currentTimeMillis() - start; System.out.println("RESULT[hikari-pool-exhaustion]: maximumPoolSize=1, connectionTimeout=1000ms | " + "second getConnection() waited=" + waitedMs + "ms before throwing " + thrown.getClass().getName() + ": " + thrown.getMessage()); assertThat(thrown).isInstanceOf(SQLTransientConnectionException.class); assertThat(thrown.getMessage()) .as("the exception message names the pool and states it's a connection-timeout, " + "not a generic SQL error -- this is what to grep application logs for") .contains("exhaustion-pool") .contains("Connection is not available"); assertThat(waitedMs) .as("the caller waits roughly connectionTimeout, not forever and not instantly") .isGreaterThanOrEqualTo(900); held.close(); // With the held connection released, a new request succeeds immediately. try (Connection recovered = dataSource.getConnection()) { assertThat(recovered.isValid(1)).isTrue(); } } private static Throwable catchThrowable(ThrowingCallable callable) { try { callable.call(); return null; } catch (Throwable t) { return t; } } @FunctionalInterface private interface ThrowingCallable { void call() throws Exception; } }