Add the following features
authorfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 30 Apr 2009 19:41:07 +0000 (19:41 +0000)
committerfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 30 Apr 2009 19:41:07 +0000 (19:41 +0000)
- max age for a connection kept alive
- an ability to reset the abandon timer when queries are executed
- an abandoned test, abandon when a percentage of the pool has been utilized

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@770411 13f79535-47bb-0310-9956-ffa450edef68

modules/jdbc-pool/doc/jdbc-pool.xml
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSource.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/PooledConnection.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPoolMBean.java
modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/AbandonPercentageTest.java [new file with mode: 0644]

index 84cf4ea..a0a54bf 100644 (file)
           that has over 200 source files(last time we checked), Tomcat jdbc has a core of 8 files, the connection pool itself is about half 
           that. As bugs may occur, they will be faster to track down, and easier to fix. Complexity reduction has been a focus from inception.</li>
       <li>Asynchronous connection retrieval - you can queue your request for a connection and receive a Future&lt;Connection&gt; back.</li>    
+      <li>Better idle connection handling. Instead of closing connections directly, it can still pool connections and sizes the idle pool with a smarter algorithm.</li>    
+      <li>You can decide at what moment connections are considered abandoned, is it when the pool is full, or directly at a timeout
+          by specifying a threshold.
+      </li>    
+      <li>The abandon connection timer will reset upon a statement/query activity. Allowing a connections that is in use for a long time to not timeout.
+          This is achieved using the ResetAbandonedTimer
+      </li>    
+      <li>Close connections after they have been connected for a certain time. Age based close upon return to the pool.
+      </li>    
     </ol>
   </p>
 
          as <code>removeAbandonedTimeout</code> has been reached.</p>
     </attribute>
 
+    <attribute name="maxAge" required="false">
+      <p>(long) Time in milliseconds to keep this connection. When a connection is returned to the pool,
+         the pool will check to see if the <code>now - time-when-connected > maxAge</code> has been reached,
+         and if so, it closes the connection rather than returning it to the pool.
+         The default value is <code>0</code>, which implies that connections will be left open and no age check
+         will be done upon returning the connection to the pool.</p>
+    </attribute>
+
     <attribute name="useEquals" required="false">
       <p>(boolean) Set to true if you wish the <code>ProxyConnection</code> class to use <code>String.equals</code> instead of 
          <code>==</code> when comparing method names. This property does not apply to added interceptors as those are configured individually.
index f5a39a2..1989d5e 100644 (file)
@@ -609,6 +609,17 @@ public class ConnectionPool {
         }
     }
 
+    protected boolean shouldClose(PooledConnection con, int action) {
+        if (con.isDiscarded()) return true;
+        if (isClosed()) return true;
+        if (!con.validate(action)) return true;
+        if (getPoolProperties().getMaxAge()>0 ) {
+            return (System.currentTimeMillis()-con.getLastConnected()) > getPoolProperties().getMaxAge();
+        } else {
+            return false;
+        }
+    }
+    
     /**
      * Returns a connection to the pool
      * @param con PooledConnection
@@ -626,8 +637,8 @@ public class ConnectionPool {
                 con.lock();
 
                 if (busy.remove(con)) {
-                    if ((!con.isDiscarded()) && (!isClosed()) &&
-                            con.validate(PooledConnection.VALIDATE_RETURN)) {
+                    
+                    if (!shouldClose(con,PooledConnection.VALIDATE_RETURN)) {
                         con.setStackTrace(null);
                         con.setTimestamp(System.currentTimeMillis());
                         if (((idle.size()>=poolProperties.getMaxIdle()) && !poolProperties.isPoolSweeperEnabled()) || (!idle.offer(con))) {
@@ -654,12 +665,11 @@ public class ConnectionPool {
         } //end if
     } //checkIn
 
-    public boolean shouldAbandon() {
+    protected boolean shouldAbandon() {
         if (poolProperties.getAbandonWhenPercentageFull()==0) return true;
         float used = (float)busy.size();
         float max  = (float)poolProperties.getMaxActive();
         float perc = (float)poolProperties.getAbandonWhenPercentageFull();
-        System.out.println("Abandon rate:"+(used/max*100f));
         return (used/max*100f)>=perc;
     }
     
index 65eb82d..c4cc4af 100644 (file)
@@ -249,6 +249,14 @@ public class DataSource extends DataSourceProxy implements MBeanRegistration,jav
             throw new RuntimeException(x);
         }
     }
+    
+    public long getMaxAge() {
+        try {
+            return createPool().getPoolProperties().getMaxAge();
+        }catch (SQLException x) {
+            throw new RuntimeException(x);
+        }
+    }    
 
     public String getName() {
         try {
index ac09e20..54e45c8 100644 (file)
@@ -78,6 +78,7 @@ public class DataSourceFactory implements ObjectFactory {
     protected final static String PROP_MINIDLE = "minIdle";
     protected final static String PROP_INITIALSIZE = "initialSize";
     protected final static String PROP_MAXWAIT = "maxWait";
+    protected final static String PROP_MAXAGE = "maxAge";
     
     protected final static String PROP_TESTONBORROW = "testOnBorrow";
     protected final static String PROP_TESTONRETURN = "testOnReturn";
@@ -149,7 +150,8 @@ public class DataSourceFactory implements ObjectFactory {
         PROP_FAIR_QUEUE,
         PROP_USE_EQUALS,
         OBJECT_NAME,
-        PROP_ABANDONWHENPERCENTAGEFULL
+        PROP_ABANDONWHENPERCENTAGEFULL,
+        PROP_MAXAGE
     };
 
     // -------------------------------------------------- ObjectFactory Methods
@@ -417,6 +419,11 @@ public class DataSourceFactory implements ObjectFactory {
             poolProperties.setAbandonWhenPercentageFull(Integer.parseInt(value));
         }
         
+        value = properties.getProperty(PROP_MAXAGE);
+        if (value != null) {
+            poolProperties.setMaxAge(Long.parseLong(value));
+        }
+        
         return poolProperties;
     }
 
index 37e01d2..9d47b9b 100644 (file)
@@ -71,6 +71,7 @@ public class PoolProperties {
     protected boolean fairQueue = true;
     protected boolean useEquals = false;
     protected int abandonWhenPercentageFull = 0;
+    protected long maxAge = 0;
 
     private InterceptorDefinition[] interceptors = null;
     
@@ -523,4 +524,14 @@ public class PoolProperties {
         this.useEquals = useEquals;
     }
 
+    public long getMaxAge() {
+        return maxAge;
+    }
+
+    public void setMaxAge(long maxAge) {
+        this.maxAge = maxAge;
+    }
+    
+    
+
 }
index bc051d3..429e6af 100644 (file)
@@ -86,6 +86,10 @@ public class PooledConnection {
      */
     protected boolean discarded = false;
     /**
+     * The Timestamp when the last time the connect() method was called successfully
+     */
+    protected volatile long lastConnected = -1;
+    /**
      * timestamp to keep track of validation intervals
      */
     protected long lastValidated = System.currentTimeMillis();
@@ -163,6 +167,7 @@ public class PooledConnection {
             if (poolProperties.getDefaultTransactionIsolation()!=DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION) connection.setTransactionIsolation(poolProperties.getDefaultTransactionIsolation());
         }        
         this.discarded = false;
+        this.lastConnected = System.currentTimeMillis();
     }
     
     /**
@@ -193,6 +198,7 @@ public class PooledConnection {
             }
         }
         connection = null;
+        lastConnected = -1;
         if (finalize) parent.finalize(this);
     }
 
@@ -381,6 +387,12 @@ public class PooledConnection {
     public java.sql.Connection getConnection() {
         return this.connection;
     }
+    
+    
+
+    public long getLastConnected() {
+        return lastConnected;
+    }
 
     /**
      * Returns the first handler in the interceptor chain
index cdc18a1..577d887 100644 (file)
@@ -276,5 +276,8 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co
     public int getAbandonWhenPercentageFull() {
         return pool.getPoolProperties().getAbandonWhenPercentageFull();
     }
+    public long getMaxAge() {
+        return pool.getPoolProperties().getMaxAge();
+    }
 
 }
diff --git a/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/AbandonPercentageTest.java b/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/AbandonPercentageTest.java
new file mode 100644 (file)
index 0000000..3f30b6b
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ *  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.tomcat.jdbc.test;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer;
+
+public class AbandonPercentageTest extends DefaultTestCase {
+
+    public AbandonPercentageTest(String name) {
+        super(name);
+    }
+    
+    public void testDefaultAbandon() throws Exception {
+        this.init();
+        this.datasource.setMaxActive(100);
+        this.datasource.setMaxIdle(100);
+        this.datasource.setInitialSize(0);
+        this.datasource.getPoolProperties().setAbandonWhenPercentageFull(0);
+        this.datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(100);
+        this.datasource.getPoolProperties().setRemoveAbandoned(true);
+        this.datasource.getPoolProperties().setRemoveAbandonedTimeout(1);
+        Connection con = datasource.getConnection();
+        long start = System.currentTimeMillis();
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        Thread.sleep(2000);
+        assertEquals("Number of connections active/busy should be 0",0,datasource.getPool().getActive());
+        con.close();
+    }
+    
+    public void testMaxedOutAbandon() throws Exception {
+        int size = 100;
+        this.init();
+        this.datasource.setMaxActive(size);
+        this.datasource.setMaxIdle(size);
+        this.datasource.setInitialSize(0);
+        this.datasource.getPoolProperties().setAbandonWhenPercentageFull(100);
+        this.datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(100);
+        this.datasource.getPoolProperties().setRemoveAbandoned(true);
+        this.datasource.getPoolProperties().setRemoveAbandonedTimeout(1);
+        Connection con = datasource.getConnection();
+        long start = System.currentTimeMillis();
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        Thread.sleep(2000);
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        con.close();
+    }
+
+    public void testResetConnection() throws Exception {
+        int size = 1;
+        this.init();
+        this.datasource.setMaxActive(size);
+        this.datasource.setMaxIdle(size);
+        this.datasource.setInitialSize(0);
+        this.datasource.getPoolProperties().setAbandonWhenPercentageFull(100);
+        this.datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(100);
+        this.datasource.getPoolProperties().setRemoveAbandoned(true);
+        this.datasource.getPoolProperties().setRemoveAbandonedTimeout(1);
+        this.datasource.getPoolProperties().setJdbcInterceptors(ResetAbandonedTimer.class.getName());
+        Connection con = datasource.getConnection();
+        long start = System.currentTimeMillis();
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        for (int i=0; i<20; i++) {
+            Thread.sleep(200);
+            con.isClosed();
+        }
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        con.close();
+    }
+
+    public void testHalfway() throws Exception {
+        int size = 100;
+        this.init();
+        this.datasource.setMaxActive(size);
+        this.datasource.setMaxIdle(size);
+        this.datasource.setInitialSize(0);
+        this.datasource.getPoolProperties().setAbandonWhenPercentageFull(50);
+        this.datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(500);
+        this.datasource.getPoolProperties().setRemoveAbandoned(true);
+        this.datasource.getPoolProperties().setRemoveAbandonedTimeout(1);
+        Connection[] con = new Connection[size];
+        con[0] = datasource.getConnection();
+        long start = System.currentTimeMillis();
+        assertEquals("Number of connections active/busy should be 1",1,datasource.getPool().getActive());
+        for (int i=1; i<25; i++) {
+            con[i] = datasource.getConnection();
+        }
+        assertEquals("Number of connections active/busy should be 25",25,datasource.getPool().getActive());
+        Thread.sleep(2500);
+        assertEquals("Number of connections active/busy should be 25",25,datasource.getPool().getActive());
+        for (int i=25; i<con.length; i++) {
+            con[i] = datasource.getConnection();
+        }
+        int active = datasource.getPool().getActive();
+        System.out.println("Active!:"+active);
+        assertEquals("Number of connections active/busy should be "+con.length,con.length,datasource.getPool().getActive());
+        Thread.sleep(2500);
+        assertEquals("Number of connections active/busy should be 0",0,datasource.getPool().getActive());
+    }
+}