package com.vladmihalcea.flexypool.strategy;
import com.vladmihalcea.flexypool.adaptor.PoolAdapter;
import com.vladmihalcea.flexypool.common.ConfigurationProperties;
import com.vladmihalcea.flexypool.connection.ConnectionRequestContext;
import com.vladmihalcea.flexypool.exception.AcquireTimeoutException;
import com.vladmihalcea.flexypool.metric.Histogram;
import com.vladmihalcea.flexypool.metric.Metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* <code>IncrementPoolOnTimeoutConnectionAcquiringStrategy</code> extends the {@link AbstractConnectionAcquiringStrategy}
* and it allows the pool size to grow beyond its {@link com.vladmihalcea.flexypool.adaptor.PoolAdapter#getMaxPoolSize()}
* up to reaching the {@link IncrementPoolOnTimeoutConnectionAcquiringStrategy#maxOverflowPoolSize} limit.
* <p>
* Use this strategy to dynamically adjust the pool size based on the connection acquiring demand.
*
* @author Vlad Mihalcea
* @since 1.0
*/
public final class IncrementPoolOnTimeoutConnectionAcquiringStrategy<T extends DataSource> extends AbstractConnectionAcquiringStrategy {
public static final String MAX_POOL_SIZE_HISTOGRAM = "maxPoolSizeHistogram";
public static final String OVERFLOW_POOL_SIZE_HISTOGRAM = "overflowPoolSizeHistogram";
private static final Logger LOGGER = LoggerFactory.getLogger(IncrementPoolOnTimeoutConnectionAcquiringStrategy.class);
/**
* The {@link com.vladmihalcea.flexypool.strategy.IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory} class allows
* creating this strategy for a given {@link ConfigurationProperties}
*/
public static class Factory<T extends DataSource> implements ConnectionAcquiringStrategyFactory<IncrementPoolOnTimeoutConnectionAcquiringStrategy, T> {
private final int maxOverflowPoolSize;
private final int timeoutMillis;
public Factory(int maxOverflowPoolSize, int timeoutMillis) {
this.maxOverflowPoolSize = maxOverflowPoolSize;
this.timeoutMillis = timeoutMillis;
}
public Factory(int maxOverflowPoolSize) {
this(maxOverflowPoolSize, Integer.MAX_VALUE);
}
/**
* Creates a {@link com.vladmihalcea.flexypool.strategy.IncrementPoolOnTimeoutConnectionAcquiringStrategy} for a given
* {@link ConfigurationProperties}
*
* @param configurationProperties configurationProperties
* @return strategy
*/
public IncrementPoolOnTimeoutConnectionAcquiringStrategy newInstance(ConfigurationProperties<T, Metrics, PoolAdapter<T>> configurationProperties) {
return new IncrementPoolOnTimeoutConnectionAcquiringStrategy(
configurationProperties, maxOverflowPoolSize, timeoutMillis
);
}
}
private final Lock lock = new ReentrantLock();
private final int maxOverflowPoolSize;
private final int timeoutMillis;
private AtomicLong overflowPoolSize = new AtomicLong();
private final Histogram maxPoolSizeHistogram;
private final Histogram overflowPoolSizeHistogram;
private final PoolAdapter poolAdapter;
/**
* Create the strategy for the given configurationProperties and the maxOverflowPoolSize.
*
* @param configurationProperties configurationProperties
* @param maxOverflowPoolSize maximum overflowing pool sizing
* @param timeoutMillis if the connection acquiring time took more than this value a pool size increment is attempted
*/
private IncrementPoolOnTimeoutConnectionAcquiringStrategy(ConfigurationProperties<? extends DataSource, Metrics, PoolAdapter> configurationProperties, int maxOverflowPoolSize, int timeoutMillis) {
super(configurationProperties);
this.maxOverflowPoolSize = maxOverflowPoolSize;
this.timeoutMillis = timeoutMillis;
this.maxPoolSizeHistogram = configurationProperties.getMetrics().histogram(MAX_POOL_SIZE_HISTOGRAM);
this.overflowPoolSizeHistogram = configurationProperties.getMetrics().histogram(OVERFLOW_POOL_SIZE_HISTOGRAM);
maxPoolSizeHistogram.update(configurationProperties.getPoolAdapter().getMaxPoolSize());
poolAdapter = configurationProperties.getPoolAdapter();
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection(ConnectionRequestContext requestContext) throws SQLException {
do {
int expectingMaxSize = poolAdapter.getMaxPoolSize();
try {
long startNanos = System.nanoTime();
Connection connection = getConnectionFactory().getConnection(requestContext);
long endNanos = System.nanoTime();
long connectionAcquireDurationMillis = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos);
if (connectionAcquireDurationMillis > timeoutMillis) {
LOGGER.warn("Connection was acquired in {} millis, timeoutMillis is set to {}",
connectionAcquireDurationMillis, timeoutMillis);
int maxPoolSize = poolAdapter.getMaxPoolSize();
if (maxPoolSize < maxOverflowPoolSize) {
if (!incrementPoolSize(expectingMaxSize)) {
LOGGER.warn("Can't acquire connection, pool size has already overflown to its max size.");
}
} else {
LOGGER.info("Pool size has already overflown to its max size of {}", maxPoolSize);
}
}
return connection;
} catch (AcquireTimeoutException e) {
if (!incrementPoolSize(expectingMaxSize)) {
LOGGER.warn("Can't acquire connection due to adaptor timeout, pool size has already overflown to its max size.");
throw e;
}
}
} while (true);
}
/**
* Attempt to increment the pool size. If the maxSize changes, it skips the incrementing process.
*
* @return if it succeeded changing the pool size
*/
protected boolean incrementPoolSize(int expectingMaxSize) {
Integer maxSize = null;
long currentOverflowPoolSize;
try {
lock.lockInterruptibly();
int currentMaxSize = poolAdapter.getMaxPoolSize();
boolean incrementMaxPoolSize = currentMaxSize < maxOverflowPoolSize;
if (currentMaxSize > expectingMaxSize) {
LOGGER.info("Pool size changed by other thread, expected {} and actual value {}", expectingMaxSize, currentMaxSize);
return incrementMaxPoolSize;
}
if (!incrementMaxPoolSize) {
return false;
}
currentOverflowPoolSize = overflowPoolSize.incrementAndGet();
poolAdapter.setMaxPoolSize(++currentMaxSize);
maxSize = currentMaxSize;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
lock.unlock();
}
LOGGER.info("Pool size changed from previous value {} to {}", expectingMaxSize, maxSize);
maxPoolSizeHistogram.update(maxSize);
overflowPoolSizeHistogram.update(currentOverflowPoolSize);
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "IncrementPoolOnTimeoutConnectionAcquiringStrategy{" +
"maxOverflowPoolSize=" + maxOverflowPoolSize +
'}';
}
}