Package com.avaje.ebeaninternal.server.lib.sql

Source Code of com.avaje.ebeaninternal.server.lib.sql.PooledConnectionQueue

package com.avaje.ebeaninternal.server.lib.sql;

import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool.Status;
import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;

public class PooledConnectionQueue {

    private static final Logger logger = LoggerFactory.getLogger(PooledConnectionQueue.class);
   
    private static final TimeUnit MILLIS_TIME_UNIT = TimeUnit.MILLISECONDS;

    private final String name;
   
    private final DataSourcePool pool;
       
    /**
     * A 'circular' buffer designed specifically for free connections.
     */
    private final FreeConnectionBuffer freeList;
   
    /**
     * A 'slots' buffer designed specifically for busy connections.
     * Fast add remove based on slot id.
     */
    private final BusyConnectionBuffer busyList;

    /**
     * Load statistics collected off connections that have closed fully (left the pool).
     */
    private final PooledConnectionStatistics collectedStats = new PooledConnectionStatistics();
   
    /**
     * Currently accumulated load statistics.
     */
    private LoadValues accumulatedValues = new LoadValues();

    /**
     * Main lock guarding all access
     */
    private final ReentrantLock lock;
   
    /**
     * Condition for threads waiting to take a connection
     */
    private final Condition notEmpty;

    private int connectionId;

    private final long waitTimeoutMillis;
   
    private final long leakTimeMinutes;
   
    private final long maxAgeMillis;

    private int warningSize;
   
    private int maxSize;
   
    private int minSize;
   
    /**
     * Number of threads in the wait queue.
     */
    private int waitingThreads;
   
    /**
     * Number of times a thread had to wait.
     */
    private int waitCount;
   
    /**
     * Number of times a connection was got from this queue.
     */
    private int hitCount;
   
    /**
     * The high water mark for the queue size.
     */
    private int highWaterMark;
   
    /**
     * Last time the pool was reset. Used to close busy connections as they are
     * returned to the pool that where created prior to the lastResetTime.
     */
    private long lastResetTime;

    private boolean doingShutdown;

    public PooledConnectionQueue(DataSourcePool pool) {
       
        this.pool = pool;
        this.name = pool.getName();
        this.minSize = pool.getMinSize();
        this.maxSize = pool.getMaxSize();
       
        this.warningSize = pool.getWarningSize();
        this.waitTimeoutMillis = pool.getWaitTimeoutMillis();
        this.leakTimeMinutes = pool.getLeakTimeMinutes();
        this.maxAgeMillis = pool.getMaxAgeMillis();

        this.busyList = new BusyConnectionBuffer(maxSize, 20);
        this.freeList = new FreeConnectionBuffer();

        this.lock = new ReentrantLock(false);
        this.notEmpty = lock.newCondition();
    }
   
    private Status createStatus() {
        return new Status(name, minSize, maxSize, freeList.size(), busyList.size(), waitingThreads, highWaterMark, waitCount, hitCount);       
    }
   
    public String toString() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return createStatus().toString();
        } finally {
            lock.unlock();
        }
    }
   
    /**
     * Collect statistics of a connection that is fully closing
     */
    protected void reportClosingConnection(PooledConnection pooledConnection) {
     
      collectedStats.add(pooledConnection.getStatistics());
    }

    public DataSourcePoolStatistics getStatistics(boolean reset) {
     
      final ReentrantLock lock = this.lock;
      lock.lock();
      try {

        LoadValues aggregate = collectedStats.getValues(reset);

        freeList.collectStatistics(aggregate, reset);
        busyList.collectStatistics(aggregate, reset);

        aggregate.plus(accumulatedValues);
       
        this.accumulatedValues = (reset) ? new LoadValues() : aggregate;
       
        return new DataSourcePoolStatistics(aggregate.getCollectionStart(), aggregate.getCount(), aggregate.getErrorCount(), aggregate.getHwmMicros(), aggregate.getTotalMicros());
       
      } finally {
          lock.unlock();
      }
  }
   
    public Status getStatus(boolean reset) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Status s = createStatus();
            if (reset){
                highWaterMark = busyList.size();
                hitCount = 0;
                waitCount = 0;
            }
            return s;
        } finally {
            lock.unlock();
        }
    }
   
    public void setMinSize(int minSize) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (minSize > this.maxSize){
                throw new IllegalArgumentException("minSize "+minSize+" > maxSize "+this.maxSize);
            }
            this.minSize = minSize;
        } finally {
            lock.unlock();
        }
    }
   
    public void setMaxSize(int maxSize) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (maxSize < this.minSize){
                throw new IllegalArgumentException("maxSize "+maxSize+" < minSize "+this.minSize);
            }
            this.busyList.setCapacity(maxSize);
            this.maxSize = maxSize;
        } finally {
            lock.unlock();
        }
    }
   
    public void setWarningSize(int warningSize) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (warningSize > this.maxSize){
                throw new IllegalArgumentException("warningSize "+warningSize+" > maxSize "+this.maxSize);
            }
            this.warningSize = warningSize;
        } finally {
            lock.unlock();
        }
    }
   
    private int totalConnections() {
        return freeList.size() + busyList.size();
    }
   
    public void ensureMinimumConnections() throws SQLException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int add = minSize - totalConnections();
            if (add > 0){
                for (int i = 0; i < add; i++) {
                    PooledConnection c = pool.createConnectionForQueue(connectionId++);
                    freeList.add(c);
                }
                notEmpty.signal();
            }
          
        } finally {
            lock.unlock();
        }       
    }
   
    /**
     * Return a PooledConnection.
     */
    protected void returnPooledConnection(PooledConnection c, boolean forceClose) {
       
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {           
            if (!busyList.remove(c)) {
                logger.error("Connection [{}] not found in BusyList? ", c);
            }
            if (forceClose || c.shouldTrimOnReturn(lastResetTime, maxAgeMillis)) {
                c.closeConnectionFully(false);
               
            } else {
                freeList.add(c);
                notEmpty.signal();
            }
        } finally {
            lock.unlock();
        }
    }

    private PooledConnection extractFromFreeList() {
        PooledConnection c = freeList.remove();
        registerBusyConnection(c);
        return c;
    }

    public PooledConnection getPooledConnection() throws SQLException {
       
        try {
            PooledConnection pc = _getPooledConnection();
            pc.resetForUse();
            return pc;
           
        } catch (InterruptedException e) {
            String msg = "Interrupted getting connection from pool "+e;
            throw new SQLException(msg);
        }
    }
    
    /**
     * Register the PooledConnection with the busyList.
     */
    private int registerBusyConnection(PooledConnection c) {
        int busySize = busyList.add(c);
        if (busySize > highWaterMark){
            highWaterMark = busySize;
        }
        return busySize;
    }
   
    private PooledConnection _getPooledConnection() throws InterruptedException, SQLException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            if (doingShutdown) {
                throw new SQLException("Trying to access the Connection Pool when it is shutting down");
            }
           
            // this includes attempts that fail with InterruptedException
            // or SQLException but that is ok as its only an indicator
            hitCount++;
           
            // are other threads already waiting? (they get priority)
            if (waitingThreads == 0){
               
                if (!freeList.isEmpty()){
                    // we have a free connection to return
                    return extractFromFreeList();
                }
               
                if (busyList.size() < maxSize){
                    // grow the connection pool
                    PooledConnection c = pool.createConnectionForQueue(connectionId++);
                    int busySize = registerBusyConnection(c);
                   
                    if (logger.isDebugEnabled()) {
                      logger.debug("DataSourcePool [{}] grow; id[{}] busy[{}] max[{}]", name, c.getName(), busySize, maxSize);
                    }
                    checkForWarningSize();
                    return c;
                }
            }
           
            try {
                // The pool is at maximum size. We are going to go into
                // a wait loop until connections are returned into the pool.
                waitCount++;
                waitingThreads++;
                return _getPooledConnectionWaitLoop();
            } finally {
                waitingThreads--;
            }

        } finally {
            lock.unlock();
        }
    }
   
    /**
     * Got into a loop waiting for connections to be returned to the pool.
     */
    private PooledConnection _getPooledConnectionWaitLoop() throws SQLException, InterruptedException {

        long nanos = MILLIS_TIME_UNIT.toNanos(waitTimeoutMillis);
        for (;;) {
           
            if (nanos <= 0) {
                String msg = "Unsuccessfully waited ["+waitTimeoutMillis+"] millis for a connection to be returned."
                    + " No connections are free. You need to Increase the max connections of ["+maxSize+"]"
                    + " or look for a connection pool leak using datasource.xxx.capturestacktrace=true";
                if (pool.isCaptureStackTrace()) {
                    dumpBusyConnectionInformation();
                }

                throw new SQLException(msg);
            }
           
            try {
                nanos = notEmpty.awaitNanos(nanos);
                if (!freeList.isEmpty()) {
                    // successfully waited
                    return extractFromFreeList();
                }
            } catch (InterruptedException ie) {
                notEmpty.signal(); // propagate to non-interrupted thread
                throw ie;
            }
        }
    }
   
    public void shutdown() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            doingShutdown = true;
            Status status = createStatus();
            DataSourcePoolStatistics statistics = pool.getStatistics(false);
            logger.debug("DataSourcePool [{}] shutdown {} - Statistics {}", name, status, statistics);
           
            closeFreeConnections(true);
       
            if (!busyList.isEmpty()) {
                logger.warn("Closing busy connections on shutdown size: "+ busyList.size());
                dumpBusyConnectionInformation();
                closeBusyConnections(0);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Close all the connections in the pool and any current busy connections
     * when they are returned. New connections will be then created on demand.
     * <p>
     * This is typically done when a database down event occurs.
     * </p>
     */
    public void reset(long leakTimeMinutes) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Status status = createStatus();
            logger.info("Reseting DataSourcePool [{}] {}", name, status);
            lastResetTime = System.currentTimeMillis();

            closeFreeConnections(false);
            closeBusyConnections(leakTimeMinutes);

            logger.info("Busy Connections:\n" + getBusyConnectionInformation());

        } finally {
            lock.unlock();
        }
    }

    public void trim(long maxInactiveMillis, long maxAgeMillis) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (trimInactiveConnections(maxInactiveMillis, maxAgeMillis) > 0) {
              try {
                ensureMinimumConnections();
              } catch (SQLException e) {
                logger.error("Error trying to ensure minimum connections", e);
              }
            }
        } finally {
            lock.unlock();
        }
    }
   
    /**
     * Trim connections that have been not used for some time.
     */
    private int trimInactiveConnections(long maxInactiveMillis, long maxAgeMillis) {
           
        long usedSince = System.currentTimeMillis() - maxInactiveMillis;
        long createdSince = (maxAgeMillis == 0) ? 0 : System.currentTimeMillis() - maxAgeMillis;
       
        int trimedCount = freeList.trim(usedSince, createdSince);
        if (trimedCount > 0) {
            logger.debug("DataSourcePool [{}] trimmed [{}] inactive connections. New size[{}]", name, trimedCount, totalConnections());
        }
        return trimedCount;
    }
   
    /**
     * Close all the connections that are in the free list.
     */
    public void closeFreeConnections(boolean logErrors) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
          freeList.closeAll(logErrors);
        } finally {
            lock.unlock();
        }
    }
   
    /**
     * Close any busy connections that have not been used for some time.
     * <p>
     * These connections are considered to have leaked from the connection pool.
     * </p>
     * <p>
     * Connection leaks occur when code doesn't ensure that connections are
     * closed() after they have been finished with. There should be an
     * appropriate try catch finally block to ensure connections are always
     * closed and put back into the pool.
     * </p>
     */
    public void closeBusyConnections(long leakTimeMinutes) {

        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            busyList.closeBusyConnections(leakTimeMinutes);
        } finally {
            lock.unlock();
        }
    }
   
    /**
     * As the pool grows it gets closer to the maxConnections limit. We can send
     * an Alert (or warning) as we get close to this limit and hence an
     * Administrator could increase the pool size if desired.
     * <p>
     * This is called whenever the pool grows in size (towards the max limit).
     * </p>
     */
    private void checkForWarningSize() {

        // the the total number of connections that we can add
        // to the pool before it hits the maximum
        int availableGrowth = (maxSize - totalConnections());

        if (availableGrowth < warningSize) {

            closeBusyConnections(leakTimeMinutes);

            String msg = "DataSourcePool [" + name + "] is [" + availableGrowth+ "] connections from its maximum size.";
            pool.notifyWarning(msg)
        }
    }
   
    public String getBusyConnectionInformation() {
        return getBusyConnectionInformation(false);
    }
   
    public void dumpBusyConnectionInformation() {
        getBusyConnectionInformation(true);
    }
   
    /**
     * Returns information describing connections that are currently being used.
     */
    private String getBusyConnectionInformation(boolean toLogger) {
       
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {

          return busyList.getBusyConnectionInformation(toLogger);
         
        } finally {
            lock.unlock();
        }
    }
       
}

TOP

Related Classes of com.avaje.ebeaninternal.server.lib.sql.PooledConnectionQueue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.