Skip to content

Commit

Permalink
4.0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
Foulest committed May 5, 2024
1 parent df2efa9 commit 22c800d
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 50 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = 'com.zaxxer'
version = '4.0.8'
version = '4.0.9'
description = project.name

compileJava.options.encoding = 'UTF-8'
Expand Down
89 changes: 74 additions & 15 deletions src/main/java/com/zaxxer/hikari/HikariConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.util.Credentials;
import com.zaxxer.hikari.util.PropertyElf;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -28,6 +29,7 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
import static com.zaxxer.hikari.util.UtilityElf.safeIsAssignableFrom;
Expand All @@ -46,6 +48,7 @@ public class HikariConfig implements HikariConfigMXBean {
private static final long SOFT_TIMEOUT_FLOOR = Long.getLong("com.zaxxer.hikari.timeoutMs.floor", 250L);
private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
private static final long MAX_LIFETIME = MINUTES.toMillis(30);
private static final double DEFAULT_MAX_LIFETIME_VARIANCE = 2.5;
private static final long DEFAULT_KEEPALIVE_TIME = 0L;
private static final long MIN_LIFETIME = SECONDS.toMillis(30);
private static final long MIN_KEEPALIVE_TIME = MIN_LIFETIME; // Assuming the same minimum as lifetime for simplicity
Expand All @@ -58,13 +61,13 @@ public class HikariConfig implements HikariConfigMXBean {

// Properties changeable at runtime through the HikariConfigMXBean
private volatile String catalog;
private volatile String username;
private volatile String password;
private final AtomicReference<Credentials> credentials = new AtomicReference<>(Credentials.of(null, null));
private volatile long connectionTimeout;
private volatile long validationTimeout;
private volatile long idleTimeout;
private volatile long leakDetectionThreshold;
private volatile long maxLifetime;
private volatile double maxLifetimeVariance;
private volatile int maximumPoolSize;
private volatile int minimumIdle;
private long initializationFailTimeout;
Expand All @@ -74,6 +77,7 @@ public class HikariConfig implements HikariConfigMXBean {
private String dataSourceJNDI;
private String driverClassName;
private String exceptionOverrideClassName;
private SQLExceptionOverride exceptionOverride;
private String jdbcUrl;
private String poolName;
private String schema;
Expand Down Expand Up @@ -105,6 +109,7 @@ public HikariConfig() {
minimumIdle = -1;
maximumPoolSize = -1;
maxLifetime = MAX_LIFETIME;
maxLifetimeVariance = DEFAULT_MAX_LIFETIME_VARIANCE;
connectionTimeout = CONNECTION_TIMEOUT;
validationTimeout = VALIDATION_TIMEOUT;
idleTimeout = IDLE_TIMEOUT;
Expand Down Expand Up @@ -171,6 +176,43 @@ public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}

public String getPassword() {
return credentials.get().getPassword();
}

@Override
public void setPassword(String password) {
credentials.updateAndGet(current -> Credentials.of(current.getUsername(), password));
}

public String getUsername() {
return credentials.get().getUsername();
}

@Override
public void setUsername(String username) {
credentials.updateAndGet(current -> Credentials.of(username, current.getPassword()));
}

/**
* Atomically set the default username and password to use for DataSource.getConnection(username, password) calls.
*
* @param credentials the username and password pair
*/
@Override
public void setCredentials(Credentials credentials) {
this.credentials.set(credentials);
}

/**
* Atomically get the default username and password to use for DataSource.getConnection(username, password) calls.
*
* @return the username and password pair
*/
public Credentials getCredentials() {
return credentials.get();
}

@Override
public void setMinimumIdle(int minimumIdle) {
if (minimumIdle < 0) {
Expand Down Expand Up @@ -506,14 +548,29 @@ public void setExceptionOverrideClassName(String exceptionOverrideClassName) {
+ " in either of HikariConfig class loader or Thread context classloader");
}

if (!SQLExceptionOverride.class.isAssignableFrom(overrideClass)) {
throw new RuntimeException("Loaded SQLExceptionOverride class " + exceptionOverrideClassName + " does not implement " + SQLExceptionOverride.class.getName());
}

try {
overrideClass.getConstructor().newInstance();
exceptionOverride = (SQLExceptionOverride) overrideClass.getConstructor().newInstance();
this.exceptionOverrideClassName = exceptionOverrideClassName;
} catch (Exception ex) {
throw new RuntimeException("Failed to instantiate class " + exceptionOverrideClassName, ex);
}
}

/**
* Set the user supplied SQLExceptionOverride instance.
*
* @param exceptionOverride the user supplied SQLExceptionOverride instance
* @see SQLExceptionOverride
*/
public void setExceptionOverride(SQLExceptionOverride exceptionOverride) {
checkIfSealed();
this.exceptionOverride = exceptionOverride;
}

/**
* Set the default transaction isolation level. The specified value is the
* constant name from the <code>Connection</code> class, e.g.
Expand Down Expand Up @@ -545,16 +602,18 @@ void seal() {
*
* @param other Other {@link HikariConfig} to copy the state to.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public void copyStateTo(HikariConfig other) {
for (Field field : HikariConfig.class.getDeclaredFields()) {
if (!Modifier.isFinal(field.getModifiers())) {
field.setAccessible(true);

try {
try {
if (!Modifier.isFinal(field.getModifiers())) {
field.setAccessible(true);
field.set(other, field.get(this));
} catch (Exception ex) {
throw new RuntimeException("Failed to copy HikariConfig state: " + ex.getMessage(), ex);
} else if (field.getType().isAssignableFrom(AtomicReference.class)) {
((AtomicReference) field.get(other)).set(((AtomicReference) field.get(this)).get());
}
} catch (Exception e) {
throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
}
}

Expand All @@ -565,18 +624,18 @@ public void copyStateTo(HikariConfig other) {
// Private methods
// ***********************************************************************

private @Nullable Class<?> attemptFromContextLoader(String driverClassName) {
private @Nullable Class<?> attemptFromContextLoader(String className) {
ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();

if (threadContextClassLoader != null) {
try {
Class<?> driverClass = threadContextClassLoader.loadClass(driverClassName);
log.debug("Driver class {} found in Thread context class loader {}",
driverClassName, threadContextClassLoader);
Class<?> driverClass = threadContextClassLoader.loadClass(className);
log.debug("Class {} found in Thread context class loader {}",
className, threadContextClassLoader);
return driverClass;
} catch (ClassNotFoundException ex) {
log.debug("Driver class {} not found in Thread context class loader {}, trying classloader {}",
driverClassName, threadContextClassLoader, getClass().getClassLoader());
log.debug("Class {} not found in Thread context class loader {}, trying classloader {}",
className, threadContextClassLoader, getClass().getClassLoader());
}
}
return null;
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.zaxxer.hikari;

import com.zaxxer.hikari.util.Credentials;

/**
* The javax.management MBean for a Hikari pool configuration.
*
Expand Down Expand Up @@ -78,6 +80,24 @@ public interface HikariConfigMXBean {
*/
void setLeakDetectionThreshold(long leakDetectionThresholdMs);

/**
* This property controls the percentage of a connections maximum lifetime that will be used as variance/jitter.
* This percentage will be a maximum and random variances up to this percentage will be subtracted from the defined
* maxLifeTime value.
*
* @return the maximum percentage of a connections maxLifeTime to be utilized as a variance
*/
double getMaxLifetimeVariance();

/**
* This property controls the percentage of a connections maximum lifetime that will be used as variance/jitter.
* This percentage will be a maximum and random variances up to this percentage will be subtracted from the defined
* maxLifeTime value.
*
* @param maxLifetimeVariance the maximum percentage of maxLifeTime that should be used for variance
*/
void setMaxLifetimeVariance(double maxLifetimeVariance);

/**
* This property controls the maximum lifetime of a connection in the pool. When a connection reaches this
* timeout, even if recently used, it will be retired from the pool. An in-use connection will never be
Expand Down Expand Up @@ -152,6 +172,14 @@ public interface HikariConfigMXBean {
*/
void setUsername(String username);

/**
* Set the username and password used for authentication. Changing this at runtime will apply to new
* connections only. Altering this at runtime only works for DataSource-based connections, not Driver-class
* or JDBC URL-based connections.
*
* @param credentials the database username and password pair
*/
void setCredentials(Credentials credentials);

/**
* The name of the connection pool.
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/zaxxer/hikari/SQLExceptionOverride.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ enum Override {
default Override adjudicate(SQLException ignored) {
return Override.CONTINUE_EVICT;
}

default boolean adjudicateAnyway() {
return false;
}
}
46 changes: 29 additions & 17 deletions src/main/java/com/zaxxer/hikari/pool/HikariPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ public HikariPool(HikariConfig config) {
addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);

addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue,
poolName + " connection adder", threadFactory,
poolName + ":connection-adder", threadFactory,
new ThreadPoolExecutor.DiscardOldestPolicy());

closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize,
poolName + " connection closer", threadFactory,
poolName + ":connection-closer", threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy());

leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
Expand Down Expand Up @@ -147,6 +147,7 @@ public Connection getConnection(long hardTimeout) throws SQLException {

try {
long timeout = hardTimeout;
String dbUrl = "";

do {
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
Expand All @@ -155,11 +156,12 @@ public Connection getConnection(long hardTimeout) throws SQLException {
break; // We timed out... break and throw exception
}

dbUrl = poolEntry.connection.getMetaData().getURL();
long now = currentTime();

if (poolEntry.isMarkedEvicted()
|| (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs
&& !isConnectionAlive(poolEntry.connection))) {
&& isConnectionDead(poolEntry.connection))) {
closeConnection(poolEntry, poolEntry.isMarkedEvicted()
? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
timeout = hardTimeout - elapsedMillis(startTime);
Expand All @@ -169,7 +171,7 @@ public Connection getConnection(long hardTimeout) throws SQLException {
}
} while (timeout > 0L);
metricsTracker.recordBorrowTimeoutStats(startTime);
throw createTimeoutException(startTime);
throw createTimeoutException(startTime, dbUrl);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new SQLException(poolName + " - Interrupted during connection acquisition", ex);
Expand Down Expand Up @@ -210,7 +212,7 @@ public synchronized void shutdown() throws InterruptedException {
connectionBag.close();

ExecutorService assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(),
poolName + " connection assassinator",
poolName + ":connection-assassinator",
config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

try {
Expand Down Expand Up @@ -387,7 +389,12 @@ void logPoolState(String... prefix) {
@Override
void recycle(PoolEntry poolEntry) {
metricsTracker.recordConnectionUsage(poolEntry);
connectionBag.requite(poolEntry);

if (poolEntry.isMarkedEvicted()) {
closeConnection(poolEntry, EVICTED_CONNECTION_MESSAGE);
} else {
connectionBag.requite(poolEntry);
}
}

/**
Expand Down Expand Up @@ -419,17 +426,16 @@ int[] getPoolStateCounts() {
// ***********************************************************************

/**
* Creating new poolEntry. If maxLifetime is configured, create a future End-of-life task with 2.5% variance from
* the maxLifetime time to ensure there is no massive die-off of Connections in the pool.
* Creating new poolEntry. If maxLifetime is configured, create a future End-of-life task with configurable
* variance from the maxLifetime time to ensure there is no massive die-off of Connections in the pool.
*/
private @Nullable PoolEntry createPoolEntry() {
try {
PoolEntry poolEntry = newPoolEntry();
long maxLifetime = config.getMaxLifetime();

if (maxLifetime > 0) {
// variance up to 2.5% of the maxLifetime
long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong(maxLifetime / 40) : 0;
long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong(Math.round(maxLifetime * (config.getMaxLifetimeVariance() / 100))) : 0;
long lifetime = maxLifetime - variance;

poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
Expand Down Expand Up @@ -587,7 +593,7 @@ private boolean softEvictConnection(@NotNull PoolEntry poolEntry, String reason,
private ScheduledExecutorService initializeHouseKeepingExecutorService() {
if (config.getScheduledExecutor() == null) {
ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElseGet(() ->
new DefaultThreadFactory(poolName + " housekeeper", true)
new DefaultThreadFactory(poolName + ":housekeeper", true)
);

ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1,
Expand Down Expand Up @@ -641,9 +647,10 @@ protected void update() {
* As a side effect, log the timeout failure at DEBUG, and record the timeout failure in the metrics tracker.
*
* @param startTime the start time (timestamp) of the acquisition attempt
* @param dbUrl the attempted database url
* @return a SQLException to be thrown from {@link #getConnection()}
*/
private @NotNull SQLException createTimeoutException(long startTime) {
private @NotNull SQLException createTimeoutException(long startTime, String dbUrl) {
logPoolState("Timeout failure ");
metricsTracker.recordConnectionTimeout();

Expand All @@ -655,8 +662,13 @@ protected void update() {
}

SQLException connectionException = new SQLTransientConnectionException(poolName
+ " - Connection is not available, request timed out after "
+ elapsedMillis(startTime) + "ms.", sqlState, originalException);
+ " - Connection is not available, request timed out after " + elapsedMillis(startTime) + "ms"
+ " (total=" + getTotalConnections()
+ ", active=" + getActiveConnections()
+ ", idle=" + getIdleConnections()
+ ", waiting=" + getThreadsAwaitingConnection()
+ ", attempted=" + dbUrl + ")"
, sqlState, originalException);

if (originalException instanceof SQLException) {
connectionException.setNextException((SQLException) originalException);
Expand Down Expand Up @@ -744,14 +756,14 @@ public void run() {

// Detect retrograde time, allowing +128ms as per NTP spec.
if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) {
log.warn("{} - Retrograde clock change detected (housekeeper delta={}),"
log.info("{} - Retrograde clock change detected (housekeeper delta={}),"
+ " soft-evicting connections from pool.", poolName, elapsedDisplayString(previous, now));
previous = now;
softEvictConnections();
return;
} else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) {
// No point evicting for forward clock motion, this merely accelerates connection retirement anyway
log.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).",
log.info("{} - Thread starvation or clock leap detected (housekeeper delta={}).",
poolName, elapsedDisplayString(previous, now));
}

Expand Down Expand Up @@ -802,7 +814,7 @@ private final class KeepaliveTask implements Runnable {

public void run() {
if (connectionBag.reserve(poolEntry)) {
if (!isConnectionAlive(poolEntry.connection)) {
if (isConnectionDead(poolEntry.connection)) {
softEvictConnection(poolEntry, DEAD_CONNECTION_MESSAGE, true);
addBagItem(connectionBag.getWaitingThreadCount());
} else {
Expand Down

0 comments on commit 22c800d

Please sign in to comment.