85 lines
3.1 KiB
Java
85 lines
3.1 KiB
Java
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.
|
|
*
|
|
* <p>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;
|
|
}
|
|
}
|