All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rapleaf.jack.transaction.DbPoolManager Maven / Gradle / Ivy

There is a newer version: 1.8
Show newest version
package com.rapleaf.jack.transaction;

import com.rapleaf.jack.queries.Column;
import com.rapleaf.jack.queries.Records;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rapleaf.jack.IDb;
import com.rapleaf.jack.exception.ConnectionCreationFailureException;
import com.rapleaf.jack.exception.NoAvailableConnectionException;

class DbPoolManager implements IDbManager {
  private static final Logger LOG = LoggerFactory.getLogger(DbPoolManager.class);

  public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 3;
  public static final int DEFAULT_MIN_IDLE_CONNECTIONS = 1;
  public static final long DEFAULT_MAX_WAIT_TIME = Duration.standardSeconds(30).getMillis();
  public static final long DEFAULT_KEEP_ALIVE_TIME = Duration.standardMinutes(5).getMillis();
  public static final boolean DEFAULT_METRICS_TRACKING_ENABLED = false;

  private final DbMetricsImpl metrics;
  private final boolean metricsTrackingEnabled;
  private final GenericObjectPool connectionPool;
  private final DbPoolStatus poolStatus;

  /**
   * Create a new DbPoolManager.
   *
   * @param dbConstructor       A callable that is used to create new db connections in the connection pool.
   * @param maxTotalConnections The maximum number of connections that can be created in the pool. Negative values
   *                            mean that there is no limit.
   * @param minIdleConnections  The minimum number of idle connections to keep in the pool.
   * @param maxWaitTime         The maximum amount of time that the {@link DbPoolManager#getConnection} method
   *                            should block before throwing an exception when the pool is exhausted. Negative values
   *                            mean that the block can be infinite.
   * @param keepAliveTime       The minimum amount of time the connection may sit idle in the pool before it is
   *                            eligible for eviction (with the extra condition that at least {@code minIdleConnections}
   *                            connections remain in the pool). When this parameter is less than zero, there is no
   *                            eviction.
   */
  DbPoolManager(Callable dbConstructor, int maxTotalConnections, int minIdleConnections, long maxWaitTime, long keepAliveTime, boolean metricsTrackingEnabled) {
    GenericObjectPoolConfig config = new GenericObjectPoolConfig();
    config.setFairness(true);
    config.setBlockWhenExhausted(true);
    config.setJmxEnabled(false);
    config.setLifo(false);
    config.setTestOnCreate(false);
    config.setTestOnReturn(false);
    config.setTestOnBorrow(true);
    config.setTestWhileIdle(true);

    config.setMaxTotal(maxTotalConnections);
    config.setMaxIdle(maxTotalConnections);
    config.setMinIdle(minIdleConnections);
    config.setMaxWaitMillis(maxWaitTime);

    // run eviction thread every keepAliveTime + 500 millis
    // add 500 millis to ensure the first round of eviction starts after
    // some connections have reached keep alive time
    config.setTimeBetweenEvictionRunsMillis(keepAliveTime + 500);

    // an idle connection can be evicted after keepAliveTime millis
    config.setSoftMinEvictableIdleTimeMillis(keepAliveTime);

    // after keepAliveTime, only minIdleConnections will be kept in the pool
    // this config may need further adjustment
    config.setNumTestsPerEvictionRun(maxTotalConnections - minIdleConnections);

    this.connectionPool = new GenericObjectPool<>(new DbPoolFactory<>(dbConstructor), config);
    this.metricsTrackingEnabled = metricsTrackingEnabled;
    this.metrics = new DbMetricsImpl(config.getMaxTotal(), config.getMinIdle(), config.getMaxWaitMillis(), config.getSoftMinEvictableIdleTimeMillis());
    this.poolStatus = new DbPoolStatusImpl(connectionPool);
  }

  /**
   * @throws NoAvailableConnectionException     if there is no available connections. Users can either
   *                                            increase the max total connections or max wait time.
   * @throws IllegalStateException              if the DB manager has already been closed.
   * @throws ConnectionCreationFailureException if new DB connections cannot be created.
   */
  @Override
  public DB getConnection() {
    try {
      if (metricsTrackingEnabled) {
        metrics.update(true, connectionPool);
      }
      DB connection = connectionPool.borrowObject();
      if (metricsTrackingEnabled) {
        metrics.update(false, connectionPool);
      }
      return connection;
    } catch (NoSuchElementException e) {
      String message = "No available connection; please consider increasing wait time or total connections";
      LOG.error(message, e);
      throw new NoAvailableConnectionException(message);
    } catch (IllegalStateException e) {
      String message = "DB connection pool has been closed";
      LOG.error(message, e);
      throw new IllegalStateException(message);
    } catch (Exception e) {
      throw new ConnectionCreationFailureException("DB connection creation failed", e);
    }
  }

  @Override
  public void returnConnection(DB connection) {
    try {
      if (metricsTrackingEnabled) {
        metrics.update(false, connectionPool);
      }
      connectionPool.returnObject(connection);
      metrics.update(false, connectionPool);
    } catch (Exception e) {
      LOG.error("Return connection failed", e);
    }
  }

  @Override
  public void invalidateConnection(final DB connection) {
    try {
      if (metricsTrackingEnabled) {
        metrics.update(false, connectionPool);
      }
      connectionPool.invalidateObject(connection);
      metrics.update(false, connectionPool);
    } catch (Exception e) {
      LOG.error("Failed to invalidate connection. This will likely lead to a connection leak!", e);
    }
  }

  @Override
  public void close() {
    connectionPool.close();
  }

  @Override
  public DbMetrics getMetrics() {
    if (!metricsTrackingEnabled) {
      return new MockDbMetrics();
    } else {
      metrics.update(false, connectionPool);
      return metrics;
    }
  }

  @VisibleForTesting
  ObjectPool getConnectionPool() {
    return connectionPool;
  }

  @Override
  public DbPoolStatus getDbPoolStatus() {
    return this.poolStatus;
  }

  private static class DbPoolFactory implements PooledObjectFactory {
    private static final String VALIDATION_QUERY = "SELECT 1 AS id";
    private static final Collection VALIDATION_COLUMN_LIST =
        Collections.singletonList(Column.fromId(null));
    private final Callable dbConstructor;

    DbPoolFactory(Callable dbConstructor) {
      this.dbConstructor = dbConstructor;
    }

    @Override
    public PooledObject makeObject() throws Exception {
      DB connection = dbConstructor.call();
      connection.disableCaching();
      return new DefaultPooledObject<>(connection);
    }

    @Override
    public void destroyObject(PooledObject connection) throws IOException {
      connection.getObject().close();
    }

    @Override
    public boolean validateObject(PooledObject connection) {
      try {
        Records records = connection.getObject()
            .findBySql(VALIDATION_QUERY, Collections.emptyList(), VALIDATION_COLUMN_LIST);
        return records.size() == 1;
      } catch (IOException e) {
        return false;
      }
    }

    @Override
    public void activateObject(PooledObject connection) throws Exception {
      // ignore
    }

    @Override
    public void passivateObject(PooledObject connection) throws Exception {
      connection.getObject().setAutoCommit(true);
      connection.getObject().setBulkOperation(false);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy