* reference to mbean
*/
protected org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool = null;
-
+
+ /**
+ * counter to track how many threads are waiting for a connection
+ */
+ protected AtomicInteger waitcount = new AtomicInteger(0);
+
//===============================================================================
// PUBLIC METHODS
//===============================================================================
public String getName() {
return getPoolProperties().getPoolName();
}
+
+ /**
+ * Return the number of threads waiting for a connection
+ * @return number of threads waiting for a connection
+ */
+ public int getWaitCount() {
+ return waitcount.get();
+ }
/**
* Returns the pool properties associated with this connection pool
jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_ABANDON, trace);
}
con.abandon();
+ //we've asynchronously reduced the number of connections
+ //we could have threads stuck in idle.poll(timeout) that will never be notified
+ if (waitcount.get()>0) idle.offer(new PooledConnection(poolProperties,this));
} finally {
con.unlock();
}
maxWait = (getPoolProperties().getMaxWait()<=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait();
}
long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now));
+ waitcount.incrementAndGet();
try {
//retrieve an existing connection
con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
- Thread.interrupted();
+ Thread.interrupted();//clear the flag, and bail out
+ SQLException sx = new SQLException("Pool wait interrupted.");
+ sx.initCause(ex);
+ throw sx;
+ } finally {
+ waitcount.decrementAndGet();
}
if (maxWait==0 && con == null) { //no wait, return one if we have one
throw new SQLException("[" + Thread.currentThread().getName()+"] " +
boolean setToNull = false;
try {
con.lock();
+ if (!con.isDiscarded() && !con.isInitialized()) {
+ con.connect();
+ }
if ((!con.isDiscarded()) && con.validate(PooledConnection.VALIDATE_BORROW)) {
//set the timestamp
con.setTimestamp(now);
if ((now - time) > con.getAbandonTimeout()) {
busy.remove(con);
abandon(con);
- release(con);
setToNull = true;
} else {
//do nothing
--- /dev/null
+package org.apache.tomcat.jdbc.test;
+
+import java.util.concurrent.CountDownLatch;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.apache.tomcat.jdbc.test.CheckOutThreadTest.TestThread;
+
+/**
+ * If a connection is abandoned and closed,
+ * then that should free up a spot in the pool, and other threads
+ * that are waiting should not time out and throw an error but be
+ * able to acquire a connection, since one was just released.
+ * @author fhanik
+ *
+ */
+public class StarvationTest extends DefaultTestCase {
+
+ public StarvationTest(String name) {
+ super(name);
+ }
+
+ private void config() {
+ datasource.getPoolProperties().setMaxActive(1);
+ datasource.getPoolProperties().setMaxIdle(1);
+ datasource.getPoolProperties().setInitialSize(1);
+ datasource.getPoolProperties().setRemoveAbandoned(true);
+ datasource.getPoolProperties().setRemoveAbandonedTimeout(5);
+ datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(500);
+ datasource.getPoolProperties().setMaxWait(10000);
+ }
+
+ public void testDBCPConnectionStarvation() throws Exception {
+ init();
+ config();
+ this.transferProperties();
+ this.tDatasource.getConnection().close();
+ javax.sql.DataSource datasource = this.tDatasource;
+ Connection con1 = datasource.getConnection();
+ Connection con2 = null;
+ try {
+ con2 = datasource.getConnection();
+ try {
+ con2.setCatalog("mysql");//make sure connection is valid
+ }catch (SQLException x) {
+ assertFalse("2nd Connection is not valid:"+x.getMessage(),true);
+ }
+ assertTrue("Connection 1 should be closed.",con1.isClosed()); //first connection should be closed
+ }catch (Exception x) {
+ assertFalse("Connection got starved:"+x.getMessage(),true);
+ }finally {
+ if (con2!=null) con2.close();
+ }
+ }
+
+ public void testConnectionStarvation() throws Exception {
+ init();
+ config();
+ Connection con1 = datasource.getConnection();
+ Connection con2 = null;
+ try {
+ con2 = datasource.getConnection();
+ try {
+ con2.setCatalog("mysql");//make sure connection is valid
+ }catch (SQLException x) {
+ assertFalse("2nd Connection is not valid:"+x.getMessage(),true);
+ }
+ assertTrue("Connection 1 should be closed.",con1.isClosed()); //first connection should be closed
+ }catch (Exception x) {
+ assertFalse("Connection got starved:"+x.getMessage(),true);
+ }finally {
+ if (con2!=null) con2.close();
+ }
+ }
+
+ public void testFairConnectionStarvation() throws Exception {
+ init();
+ config();
+ datasource.getPoolProperties().setFairQueue(true);
+ Connection con1 = datasource.getConnection();
+ Connection con2 = null;
+ try {
+ con2 = datasource.getConnection();
+ try {
+ con2.setCatalog("mysql");//make sure connection is valid
+ }catch (SQLException x) {
+ assertFalse("2nd Connection is not valid:"+x.getMessage(),true);
+ }
+ assertTrue("Connection 1 should be closed.",con1.isClosed()); //first connection should be closed
+ }catch (Exception x) {
+ assertFalse("Connection got starved:"+x.getMessage(),true);
+ }finally {
+ if (con2!=null) con2.close();
+ }
+ }
+}