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

org.apache.airavata.api.server.util.ConnectionPool Maven / Gradle / Ivy

There is a newer version: 0.16
Show newest version
/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

package org.apache.airavata.api.server.util;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import java.util.concurrent.Semaphore;


/**
 * A class for preallocating, recycling, and managing JDBC connections.
 */
public class ConnectionPool {
    private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);

    private long MAX_IDLE_TIME = 5 * 60 * 1000; // 5 minutes

    private String driver;
    private String url;
    private String username;
    private String password;
    private String jdbcUrl;

    private int maxConnections;

    private boolean autoCommit = true;
    private boolean waitIfBusy;

    private Semaphore needConnection = new Semaphore(0);
    private boolean stop;

    private Stack availableConnections;
    private Stack busyConnections;

    private HashMap lastAccessTimeRecord = new HashMap();

    private String urlType = "";

    private DataSource datasource;

    private int transactionIsolation = Connection.TRANSACTION_NONE;

    private Thread clenupThread;
    private Thread producerThread;

    public ConnectionPool(String driver, String url, String username, String password, int initialConnections,
                          int maxConnections, boolean waitIfBusy) throws SQLException {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
        this.urlType = "speratedURL";
        initialize(initialConnections, maxConnections, waitIfBusy);
    }

    public ConnectionPool(String driver, String jdbcUrl, int initialConnections, int maxConnections,
                          boolean waitIfBusy, boolean autoCommit, int transactionIsolation) throws SQLException {
        this.driver = driver;
        this.jdbcUrl = jdbcUrl;
        this.urlType = "simpleURL";
        this.autoCommit = autoCommit;
        this.transactionIsolation = transactionIsolation;
        initialize(initialConnections, maxConnections, waitIfBusy);
    }

    public ConnectionPool(String driver, String jdbcUrl, int initialConnections, int maxConnections, boolean waitIfBusy)
            throws SQLException {
        this.driver = driver;
        this.jdbcUrl = jdbcUrl;
        this.urlType = "simpleURL";
        initialize(initialConnections, maxConnections, waitIfBusy);
    }

    public ConnectionPool(DataSource dataSource, int initialConnections, int maxConnections, boolean waitIfBusy)
            throws SQLException {
        this.urlType = "dataSource";
        this.datasource = dataSource;
        initialize(initialConnections, maxConnections, waitIfBusy);
    }

    /**
     * Check if this connection pool is auto commit or not
     *
     * @return
     */
    public boolean isAutoCommit() {
        return this.autoCommit;
    }

    private void initialize(int initialConnections, int maxConnections, boolean waitIfBusy) throws SQLException {
        this.maxConnections = maxConnections;
        this.waitIfBusy = waitIfBusy;

        int sizeOfConnections = (initialConnections > maxConnections) ? maxConnections : initialConnections;

        availableConnections = new Stack();
        busyConnections = new Stack();

        for (int i = 0; i < sizeOfConnections; i++) {
            Connection con = makeNewConnection();
            setTimeStamp(con);
            availableConnections.push(con);

        }

        producerThread = new Thread(new FillUpThread());
        producerThread.start();

        clenupThread = new Thread(new CleanUpThread());
        clenupThread.start();
    }

    public synchronized Connection getConnection() throws SQLException {
        if (!availableConnections.isEmpty()) {
            Connection existingConnection = availableConnections.pop();

            // If connection on available list is closed (e.g.,
            // it timed out), then remove it from available list
            // and race for a connection again.
            if (existingConnection.isClosed()) {
                lastAccessTimeRecord.remove(existingConnection);
                // notifyAll for fairness
                notifyAll();
            } else {
                busyConnections.push(existingConnection);
                setTimeStamp(existingConnection);
                return existingConnection;
            }
        } else if (!waitIfBusy && busyConnections.size() >= maxConnections) {
            // You reached maxConnections limit and waitIfBusy flag is false.
            // Throw SQLException in such a case.
            throw new SQLException("Connection limit reached");
        } else {

            if (busyConnections.size() < maxConnections) {
                // available connection is empty, but total number of connection
                // doesn't reach maxConnection. Request for more connection
                needConnection.release();
            }

            try {
                // wait for free connection
                wait();
            } catch (InterruptedException ie) {
            }
        }
        // always race for connection forever
        return getConnection();
    }

    // This explicitly makes a new connection. Called in
    // the foreground when initializing the ConnectionPool,
    // and called in the background when running.
    private Connection makeNewConnection() throws SQLException {
        try {
            // Load database driver if not already loaded
            Class.forName(driver);
            Connection connection;
            // Establish network connection to database
            if (urlType.equals("speratedURL")) {
                connection = DriverManager.getConnection(url, username, password);
            } else if (urlType.equals("simpleURL")) {
                connection = DriverManager.getConnection(jdbcUrl);
            } else { // if(urlType.equals("dataSource")){
                connection = datasource.getConnection();
            }
            connection.setTransactionIsolation(this.transactionIsolation);
            connection.setAutoCommit(this.autoCommit);
            return connection;
        } catch (ClassNotFoundException cnfe) {
            // Simplify try/catch blocks of people using this by
            // throwing only one exception type.
            throw new SQLException("Can't find class for driver: " + driver);
        }
    }

    private synchronized void fillUpConnection(Connection conn) {
        setTimeStamp(conn);
        availableConnections.push(conn);

        // notify all since new connection is created
        notifyAll();
    }

    private void setTimeStamp(Connection connection) {
        lastAccessTimeRecord.put(connection, System.currentTimeMillis());
    }

    // The database connection cannot be left idle for too long, otherwise TCP
    // connection will be broken.
    /**
     * From http://forums.mysql.com/read.php?39,28450,57460#msg-57460 Okay, then it looks like wait_timeout on the
     * server is killing your connection (it is set to 8 hours of idle time by default). Either set that value higher on
     * your server, or configure your connection pool to not hold connections idle that long (I prefer the latter). Most
     * folks I know that run MySQL with a connection pool in high-load production environments only let connections sit
     * idle for a matter of minutes, since it only takes a few milliseconds to open a connection, and the longer one
     * sits idle the more chance it will go "bad" because of a network hiccup or the MySQL server being restarted.
     *
     * @throws java.sql.SQLException
     */
    private boolean isConnectionStale(Connection connection) {
        long currentTime = System.currentTimeMillis();
        long lastAccess = lastAccessTimeRecord.get(connection);
        if (currentTime - lastAccess > MAX_IDLE_TIME) {
            return true;
        } else
            return false;
    }

    private synchronized void closeStaleConnections() {
        // close idle connections
        Iterator iter = availableConnections.iterator();
        while (iter.hasNext()) {
            Connection existingConnection = iter.next();
            if (isConnectionStale(existingConnection)) {
                try {
                    existingConnection.close();
                    iter.remove();
                } catch (SQLException sql) {
                    logger.error(sql.getMessage(), sql);
                }
            }
        }
        // close busy connections that have been checked out for too long.
        // This should not happen since this means program has bug for not
        // releasing connections .
        iter = busyConnections.iterator();
        while (iter.hasNext()) {
            Connection busyConnection = iter.next();
            if (isConnectionStale(busyConnection)) {
                try {
                    busyConnection.close();
                    iter.remove();
                    logger.warn("****Connection has checked out too long. Forced release. Check the program for calling release connection [free(Connection) method]");
                } catch (SQLException sql) {
                    logger.error(sql.getMessage(), sql);
                }
            }
        }
    }

    public synchronized void free(Connection connection) {
        busyConnections.removeElement(connection);
        availableConnections.addElement(connection);
        // Wake up threads that are waiting for a connection
        notifyAll();
    }

    /**
     * Close all the connections. Use with caution: be sure no connections are in use before calling. Note that you are
     * not required to call this when done with a ConnectionPool, since connections are guaranteed to be closed
     * when garbage collected. But this method gives more control regarding when the connections are closed.
     */
    public synchronized void dispose() {
        logger.info("Connection Pool Shutting down");

        // stop clean up thread
        this.stop = true;
        this.clenupThread.interrupt();

        // stop producer up thread
        this.producerThread.interrupt();

        // close all connection
        closeConnections(availableConnections);
        availableConnections = new Stack();
        closeConnections(busyConnections);
        busyConnections = new Stack();
        lastAccessTimeRecord.clear();

        logger.info("All connection is closed");

        try {
            this.clenupThread.join();
            this.producerThread.join();
        } catch (Exception e) {
            logger.error("Cannot shutdown cleanup thread", e);
        }

        logger.info("Connection Pool Shutdown");
    }

    private void closeConnections(Stack connections) {
        while (!connections.isEmpty()) {
            Connection connection = connections.pop();
            try {
                if (!connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException sqle) {
                // Ignore errors; garbage collect anyhow
                logger.warn(sqle.getMessage());
            }
        }
    }

    public synchronized String toString() {
        String info = "ConnectionPool(" + url + "," + username + ")" + ", available=" + availableConnections.size()
                + ", busy=" + busyConnections.size() + ", max=" + maxConnections;
        return (info);
    }

    class CleanUpThread implements Runnable {
        public void run() {
            while (!stop) {
                try {
                    Thread.sleep(MAX_IDLE_TIME);
                    closeStaleConnections();
                } catch (InterruptedException e) {
                    logger.info("Clean up thread is interrupted to close");
                }
            }
        }
    }

    class FillUpThread implements Runnable {
        public void run() {
            while (!stop) {
                try {
                    // block until get
                    needConnection.acquire();

                    Connection conn = makeNewConnection();
                    fillUpConnection(conn);
                } catch (SQLException e) {
                    // cannot create connection (increase semaphore value back)
                    needConnection.release();
                    logger.error(e.getMessage(), e);
                } catch (InterruptedException e) {
                    logger.info("Fill up thread is interrupted to close");
                    break;
                }
            }
        }
    }

    public void shutdown() throws SQLException{
        for (Connection c : availableConnections) {
            try {
                c.close();
            } catch (SQLException e) {
                logger.error("Error while closing the connection", e);
                throw new SQLException("Error while closing the connection", e);
            }
        }

        for (Connection c : busyConnections) {
            try {
                c.close();
            } catch (SQLException e) {
                logger.error("Error while closing the connection", e);
                throw new SQLException("Error while closing the connection", e);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy