Skip to content

Commit

Permalink
mysql connection pool
Browse files Browse the repository at this point in the history
  • Loading branch information
imaohw committed Sep 25, 2011
1 parent 2c64211 commit 340641b
Show file tree
Hide file tree
Showing 3 changed files with 405 additions and 77 deletions.
1 change: 1 addition & 0 deletions .hgignore
Expand Up @@ -5,6 +5,7 @@ nbproject/**
dist/**
nbproject/**
build.xml
jarlib/**
\.orig$
\.orig\..*$
\.chg\..*$
Expand Down
314 changes: 314 additions & 0 deletions src/uk/org/whoami/authme/datasource/MiniConnectionPoolManager.java
@@ -0,0 +1,314 @@
// Copyright 2007-2011 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
//
// This module is multi-licensed and may be used under the terms
// of any of the following licenses:
//
// EPL, Eclipse Public License, http://www.eclipse.org/legal
// LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html
// MPL, Mozilla Public License 1.1, http://www.mozilla.org/MPL
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.

package uk.org.whoami.authme.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.LinkedList;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

/**
* A lightweight standalone JDBC connection pool manager.
*
* <p>The public methods of this class are thread-safe.
*
* <p>Home page: <a href="http://www.source-code.biz/miniconnectionpoolmanager">www.source-code.biz/miniconnectionpoolmanager</a><br>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / MPL.
*/
public class MiniConnectionPoolManager {

private ConnectionPoolDataSource dataSource;
private int maxConnections;
private long timeoutMs;
private PrintWriter logWriter;
private Semaphore semaphore;
private LinkedList<PooledConnection> recycledConnections;
private int activeConnections;
private PoolConnectionEventListener poolConnectionEventListener;
private boolean isDisposed;
private boolean doPurgeConnection;

/**
* Thrown in {@link #getConnection()} or {@link #getValidConnection()} when no free connection becomes
* available within <code>timeout</code> seconds.
*/
public static class TimeoutException extends RuntimeException {
private static final long serialVersionUID = 1;
public TimeoutException () {
super("Timeout while waiting for a free database connection."); }
public TimeoutException (String msg) {
super(msg); }}

/**
* Constructs a MiniConnectionPoolManager object with a timeout of 60 seconds.
*
* @param dataSource
* the data source for the connections.
* @param maxConnections
* the maximum number of connections.
*/
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections) {
this(dataSource, maxConnections, 60); }

/**
* Constructs a MiniConnectionPoolManager object.
*
* @param dataSource
* the data source for the connections.
* @param maxConnections
* the maximum number of connections.
* @param timeout
* the maximum time in seconds to wait for a free connection.
*/
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections, int timeout) {
this.dataSource = dataSource;
this.maxConnections = maxConnections;
this.timeoutMs = timeout * 1000L;
try {
logWriter = dataSource.getLogWriter(); }
catch (SQLException e) {}
if (maxConnections < 1) {
throw new IllegalArgumentException("Invalid maxConnections value."); }
semaphore = new Semaphore(maxConnections,true);
recycledConnections = new LinkedList<PooledConnection>();
poolConnectionEventListener = new PoolConnectionEventListener(); }

/**
* Closes all unused pooled connections.
*/
public synchronized void dispose() throws SQLException {
if (isDisposed) {
return; }
isDisposed = true;
SQLException e = null;
while (!recycledConnections.isEmpty()) {
PooledConnection pconn = recycledConnections.remove();
try {
pconn.close(); }
catch (SQLException e2) {
if (e == null) {
e = e2; }}}
if (e != null) {
throw e; }}

/**
* Retrieves a connection from the connection pool.
*
* <p>If <code>maxConnections</code> connections are already in use, the method
* waits until a connection becomes available or <code>timeout</code> seconds elapsed.
* When the application is finished using the connection, it must close it
* in order to return it to the pool.
*
* @return
* a new <code>Connection</code> object.
* @throws TimeoutException
* when no connection becomes available within <code>timeout</code> seconds.
*/
public Connection getConnection() throws SQLException {
return getConnection2(timeoutMs); }

private Connection getConnection2 (long timeoutMs) throws SQLException {
// This routine is unsynchronized, because semaphore.tryAcquire() may block.
synchronized (this) {
if (isDisposed) {
throw new IllegalStateException("Connection pool has been disposed."); }}
try {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new TimeoutException(); }}
catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for a database connection.",e); }
boolean ok = false;
try {
Connection conn = getConnection3();
ok = true;
return conn; }
finally {
if (!ok) {
semaphore.release(); }}}

private synchronized Connection getConnection3() throws SQLException {
if (isDisposed) {
throw new IllegalStateException("Connection pool has been disposed."); } // test again with lock
PooledConnection pconn;
if (!recycledConnections.isEmpty()) {
pconn = recycledConnections.remove(); }
else {
pconn = dataSource.getPooledConnection();
pconn.addConnectionEventListener(poolConnectionEventListener); }
Connection conn = pconn.getConnection();
activeConnections++;
assertInnerState();
return conn; }

/**
* Retrieves a connection from the connection pool and ensures that it is valid
* by calling {@link Connection#isValid(int)}.
*
* <p>If a connection is not valid, the method tries to get another connection
* until one is valid (or a timeout occurs).
*
* <p>Pooled connections may become invalid when e.g. the database server is
* restarted.
*
* <p>This method is slower than {@link #getConnection()} because the JDBC
* driver has to send an extra command to the database server to test the connection.
*
*
* <p>This method requires Java 1.6 or newer.
*
* @throws TimeoutException
* when no valid connection becomes available within <code>timeout</code> seconds.
*/
public Connection getValidConnection() {
long time = System.currentTimeMillis();
long timeoutTime = time + timeoutMs;
int triesWithoutDelay = getInactiveConnections() + 1;
while (true) {
Connection conn = getValidConnection2(time, timeoutTime);
if (conn != null) {
return conn; }
triesWithoutDelay--;
if (triesWithoutDelay <= 0) {
triesWithoutDelay = 0;
try {
Thread.sleep(250); }
catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for a valid database connection.", e); }}
time = System.currentTimeMillis();
if (time >= timeoutTime) {
throw new TimeoutException("Timeout while waiting for a valid database connection."); }}}

private Connection getValidConnection2 (long time, long timeoutTime) {
long rtime = Math.max(1, timeoutTime - time);
Connection conn;
try {
conn = getConnection2(rtime); }
catch (SQLException e) {
return null; }
rtime = timeoutTime - System.currentTimeMillis();
int rtimeSecs = Math.max(1, (int)((rtime+999)/1000));
try {
if (conn.isValid(rtimeSecs)) {
return conn; }}
catch (SQLException e) {}
// This Exception should never occur. If it nevertheless occurs, it's because of an error in the
// JDBC driver which we ignore and assume that the connection is not valid.
// When isValid() returns false, the JDBC driver should have already called connectionErrorOccurred()
// and the PooledConnection has been removed from the pool, i.e. the PooledConnection will
// not be added to recycledConnections when Connection.close() is called.
// But to be sure that this works even with a faulty JDBC driver, we call purgeConnection().
purgeConnection(conn);
return null; }

// Purges the PooledConnection associated with the passed Connection from the connection pool.
private synchronized void purgeConnection (Connection conn) {
try {
doPurgeConnection = true;
// (A potential problem of this program logic is that setting the doPurgeConnection flag
// has an effect only if the JDBC driver calls connectionClosed() synchronously within
// Connection.close().)
conn.close(); }
catch (SQLException e) {}
// ignore exception from close()
finally {
doPurgeConnection = false; }}

private synchronized void recycleConnection (PooledConnection pconn) {
if (isDisposed || doPurgeConnection) {
disposeConnection(pconn);
return; }
if (activeConnections <= 0) {
throw new AssertionError(); }
activeConnections--;
semaphore.release();
recycledConnections.add(pconn);
assertInnerState(); }

private synchronized void disposeConnection (PooledConnection pconn) {
pconn.removeConnectionEventListener(poolConnectionEventListener);
if (!recycledConnections.remove(pconn)) {
// If the PooledConnection is not in the recycledConnections list,
// we assume that the connection was active.
if (activeConnections <= 0) {
throw new AssertionError(); }
activeConnections--;
semaphore.release(); }
closeConnectionAndIgnoreException(pconn);
assertInnerState(); }

private void closeConnectionAndIgnoreException (PooledConnection pconn) {
try {
pconn.close(); }
catch (SQLException e) {
log("Error while closing database connection: "+e.toString()); }}

private void log (String msg) {
String s = "MiniConnectionPoolManager: "+msg;
try {
if (logWriter == null) {
System.err.println(s); }
else {
logWriter.println(s); }}
catch (Exception e) {}}

private void assertInnerState() {
if (activeConnections < 0) {
throw new AssertionError(); }
if (activeConnections + recycledConnections.size() > maxConnections) {
throw new AssertionError(); }
if (activeConnections + semaphore.availablePermits() > maxConnections) {
throw new AssertionError(); }}

private class PoolConnectionEventListener implements ConnectionEventListener {
public void connectionClosed (ConnectionEvent event) {
PooledConnection pconn = (PooledConnection)event.getSource();
recycleConnection(pconn); }
public void connectionErrorOccurred (ConnectionEvent event) {
PooledConnection pconn = (PooledConnection)event.getSource();
disposeConnection(pconn); }}

/**
* Returns the number of active (open) connections of this pool.
*
* <p>This is the number of <code>Connection</code> objects that have been
* issued by {@link #getConnection()}, for which <code>Connection.close()</code>
* has not yet been called.
*
* @return
* the number of active connections.
**/
public synchronized int getActiveConnections() {
return activeConnections; }

/**
* Returns the number of inactive (unused) connections in this pool.
*
* <p>This is the number of internally kept recycled connections,
* for which <code>Connection.close()</code> has been called and which
* have not yet been reused.
*
* @return
* the number of inactive connections.
**/
public synchronized int getInactiveConnections() {
return recycledConnections.size(); }

} // end class MiniConnectionPoolManager

0 comments on commit 340641b

Please sign in to comment.