Here are a couple patches that you might find useful.  

Patch 1: jakarta-commons/pool (implementation of TODO in
GenericKeyedObjectPool) 
If maxTotal is specified for genericKeyedObjectPool, then clearOldest method
is called which removes the oldest 15% of the objects from the pool:
basically the cache now implements an LRU for pruning.  At some point it
would be nice if the percentage was configurable.  I will add to bugzilla as
an attachemnt if it will let me.  


Patch 2: jakarta-commons/dbcp
Currently there is not much configurability for the prepared statement
pooling.  This patch defines a new property on the driveradaptercpds class:
maxPreparedStatements.  If this value is set to something other than -1,
then the underlying statement pool will not use an evictor thread and will
use the mechanism in patch 1 to remove the oldest objects.  If set to -1,
then the old behavior is used.  You may find this patch useful.  In our
environment we are concerned about creating a separate thread for EVERY
connection.  

ToddC


-----Original Message-----
From: Dirk Verbeeck [mailto:[EMAIL PROTECTED] 
Sent: Tuesday, February 24, 2004 1:51 PM
To: Todd Carmichael
Subject: Re: Prepared Statement pooling


Todd

The code looks just fine, building a TreeMap is needed if you want to 
remove multiple items at once. A small optimalization would be to 
limit the number of items you put in the map.
(if the size>itemsToRemove remove the youngest item)
But I don't see a way to avoid the sorted map.

If you can make some JUnit tests then I will commit this weekend.

Cheers
Dirk


Todd Carmichael wrote:

> Dirk,
> Here is the code that will clear the oldest items in a pool (basically 
> an LRU).  I would appreciate any thoughts you have concerning this 
> code.  It removes the oldest 25% of the items in the cache.  The 
> number of items to remove could definitely be a configurable option.  
> Is building the TreeMap (sorting the items) necessary (i.e. is there a 
> better way to determine the oldest items)?
> 
> Thanks.
> 
>     class ObjectTimeStampComparable implements Comparator
>     {
>         public int compare(Object o1, Object o2)
>         {
>             return (int)(((ObjectTimestampPair)o1).tstamp - 
> ((ObjectTimestampPair)o2).tstamp);
>         }
>     }
>     public synchronized void clearOldest() {
> 
>         TreeMap map = new TreeMap(new ObjectTimeStampComparable());
>         for(Iterator keyiter = _poolList.iterator(); keyiter.hasNext(); )
{
>             Object key = keyiter.next();
>             CursorableLinkedList list = 
> (CursorableLinkedList)(_poolMap.get(key));
>             for(Iterator it = list.iterator(); it.hasNext(); ) {
>                 try {
>                     // each item into the map uses the 
> objectimestamppair object
>                     // as the key.  It then gets sorted based on the 
> timstamp field
>                     // each value in the map is the parent list it 
> belongs in.
>                     ObjectTimestampPair pair = 
> (ObjectTimestampPair)(it.next());
>                     map.put(pair,key);
>                 } catch(Exception e) {
>                     // ignore error, keep destroying the rest
>                 }
>             }
>         }
>         Set setPairKeys = map.entrySet();
>         int itemsToRemove = (int)(_maxTotal*.25) + 1; // 25% of items 
> removed plus one to account for zero
>         Iterator iter = setPairKeys.iterator();
>         while(iter.hasNext() && itemsToRemove>0)
>         {
>             Map.Entry entry = (Map.Entry)iter.next();
>             // kind of backwards on naming.  In the map, each key is 
> the objecttimestamppair
>             // because it has the ordering with the timestamp value.  
> Each value that the
>             // key references is the key of the list it belongs to.
>             CursorableLinkedList list = 
> (CursorableLinkedList)(_poolMap.get(entry.getValue()));
>             list.remove(entry.getKey());
>             try {
>                 _factory.destroyObject(entry.getValue(),entry.getKey());
>             }
>             catch(Exception e) {
>                     // ignore error, keep destroying the rest
>             }
>             _totalIdle--;
>             itemsToRemove--;
>         }
>         notifyAll();
>     }
> 
> 
> ToddC



Index: src/java/org/apache/commons/pool/impl/GenericKeyedObjectPool.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons/pool/src/java/org/apache/commons/pool/impl/GenericKeyedObjectPool.java,v
retrieving revision 1.26
diff -u -r1.26 GenericKeyedObjectPool.java
--- src/java/org/apache/commons/pool/impl/GenericKeyedObjectPool.java   28 Feb 2004 
11:46:33 -0000      1.26
+++ src/java/org/apache/commons/pool/impl/GenericKeyedObjectPool.java   29 Feb 2004 
00:27:26 -0000
@@ -19,6 +19,10 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.TreeMap;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.Map;
 
 import org.apache.commons.collections.CursorableLinkedList;
 import org.apache.commons.pool.BaseKeyedObjectPool;
@@ -733,10 +737,9 @@
             // otherwise
             if(null == pair) {
                 // if there is a totalMaxActive and we are at the limit then
-                // we have to make room
-                // TODO: this could be improved by only removing the oldest object
+                // we have to make room.  
                 if ((_maxTotal > 0) && (_totalActive + _totalIdle >= _maxTotal)) {
-                    clear();
+                    clearOldest();
                 }
                 
                 // check if we can create one
@@ -807,6 +810,84 @@
         _totalIdle = 0;
         notifyAll();
     }
+
+    /**
+    * implementation of comparator interface for ObjectTimeStampPair objects
+    * used in sorting all idle objects in pool so that the oldest ones can
+    * be removed
+    *
+    */
+    class ObjectTimeStampComparable implements Comparator
+    {
+        public int compare(Object o1, Object o2)
+        {
+            long iDiff = ((ObjectTimestampPair)o1).tstamp - 
((ObjectTimestampPair)o2).tstamp;
+            if (iDiff==0)
+            {
+                iDiff = o1.hashCode() - o2.hashCode();
+            }
+            return (int)iDiff;
+        }
+    }
+
+    /**
+    * 
+    * Method clears oldest 15% of objects in pool.  The method sorts the 
+    * objects into a TreeMap and then iterates the first 15% for removal
+    * 
+    *
+    */
+    public synchronized void clearOldest() {
+
+        // build sorted map of idle objects
+        TreeMap map = new TreeMap(new ObjectTimeStampComparable());
+        for(Iterator keyiter = _poolList.iterator(); keyiter.hasNext(); ) {
+            Object key = keyiter.next();
+            CursorableLinkedList list = (CursorableLinkedList)(_poolMap.get(key));
+            for(Iterator it = list.iterator(); it.hasNext(); ) {
+                try {
+                    // each item into the map uses the objectimestamppair object
+                    // as the key.  It then gets sorted based on the timstamp field
+                    // each value in the map is the parent list it belongs in.
+                    ObjectTimestampPair pair = (ObjectTimestampPair)(it.next());
+                    map.put(pair,key);
+                } catch(Exception e) {
+                    // ignore error, keep destroying the rest
+                }
+            }
+        }
+        // now iterate created map and kill the first 15%.  Should be a 
+        // configurable option.  
+        Set setPairKeys = map.entrySet();
+        int itemsToRemove = ((int)(map.size()*.15)) + 1; // 15% of items removed plus 
one to account for zero
+        Iterator iter = setPairKeys.iterator();
+        while(iter.hasNext() && itemsToRemove>0)
+        {
+            Map.Entry entry = (Map.Entry)iter.next();
+            // kind of backwards on naming.  In the map, each key is the 
objecttimestamppair
+            // because it has the ordering with the timestamp value.  Each value that 
the
+            // key references is the key of the list it belongs to.
+            Object key = entry.getValue();
+            ObjectTimestampPair pairTimeStamp = (ObjectTimestampPair) entry.getKey();
+            CursorableLinkedList list = (CursorableLinkedList)(_poolMap.get(key));
+            list.remove(pairTimeStamp);
+
+            try {
+                _factory.destroyObject(key,pairTimeStamp.value);
+            }
+            catch(Exception e) {
+                    // ignore error, keep destroying the rest
+            }
+            // if that was the last object for that key, drop that pool
+            if (list.isEmpty()) {
+                _poolMap.remove(key);
+                _poolList.remove(key);
+            }
+            _totalIdle--;
+            itemsToRemove--;
+        }
+         notifyAll();
+     }
 
     public synchronized void clear(Object key) {
         CursorableLinkedList pool = (CursorableLinkedList)(_poolMap.remove(key));
Index: src/test/org/apache/commons/pool/impl/TestGenericKeyedObjectPool.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons/pool/src/test/org/apache/commons/pool/impl/TestGenericKeyedObjectPool.java,v
retrieving revision 1.18
diff -u -r1.18 TestGenericKeyedObjectPool.java
--- src/test/org/apache/commons/pool/impl/TestGenericKeyedObjectPool.java       28 Feb 
2004 11:46:11 -0000      1.18
+++ src/test/org/apache/commons/pool/impl/TestGenericKeyedObjectPool.java       29 Feb 
2004 00:27:55 -0000
@@ -221,6 +221,51 @@
         assertEquals(0, pool.getNumIdle("b"));
     }
 
+    public void testMaxTotalLRU() throws Exception {
+        pool.setMaxActive(2);
+        pool.setMaxTotal(3);
+        pool.setWhenExhaustedAction(GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW);
+
+        Object o1 = pool.borrowObject("a");
+        assertNotNull(o1);
+        long lo1Hash = o1.hashCode();
+        pool.returnObject("a", o1);
+        Thread.sleep(10);
+
+        Object o2 = pool.borrowObject("b");
+        assertNotNull(o2);
+        pool.returnObject("b", o2);
+        Thread.sleep(10);
+
+        Object o3 = pool.borrowObject("c");
+        assertNotNull(o3);
+        pool.returnObject("c", o3);
+        Thread.sleep(10);
+
+        Object o6 = pool.borrowObject("a");
+        assertNotNull(o6);
+        long lo6Hash = o6.hashCode();
+        pool.returnObject("a", o6);
+        Thread.sleep(10);
+
+        assertTrue(lo1Hash == lo6Hash);
+
+        // this should cause a to be bumped out of the pool
+        Object o4 = pool.borrowObject("d");
+        assertNotNull(o4);
+        pool.returnObject("d", o4);
+        Thread.sleep(10);
+
+        // now re-request b, we should get a different object because it should
+        // have been expelled from pool
+        Object o5 = pool.borrowObject("b");
+        assertNotNull(o5);
+        long lo5Hash = o5.hashCode();
+        pool.returnObject("b", o5);
+        assertTrue(lo5Hash != lo1Hash);
+
+    }
+ 
 
     public void testSettersAndGetters() throws Exception {
         GenericKeyedObjectPool pool = new GenericKeyedObjectPool();
Index: src/java/org/apache/commons/dbcp/cpdsadapter/DriverAdapterCPDS.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons/dbcp/src/java/org/apache/commons/dbcp/cpdsadapter/DriverAdapterCPDS.java,v
retrieving revision 1.8
diff -u -r1.8 DriverAdapterCPDS.java
--- src/java/org/apache/commons/dbcp/cpdsadapter/DriverAdapterCPDS.java 28 Feb 2004 
12:18:17 -0000      1.8
+++ src/java/org/apache/commons/dbcp/cpdsadapter/DriverAdapterCPDS.java 29 Feb 2004 
00:26:10 -0000
@@ -113,6 +113,7 @@
     private int _timeBetweenEvictionRunsMillis = -1;
     private int _numTestsPerEvictionRun = -1;
     private int _minEvictableIdleTimeMillis = -1;
+    private int _maxPreparedStatements = -1;
 
     private boolean getConnectionCalled = false;
 
@@ -147,13 +148,28 @@
         */
         KeyedObjectPool stmtPool = null;
         if (isPoolPreparedStatements()) {
-            stmtPool = new GenericKeyedObjectPool(null,
-                getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0, 
-                getMaxIdle(), false, false, getTimeBetweenEvictionRunsMillis(),
-                getNumTestsPerEvictionRun(), 
-                getMinEvictableIdleTimeMillis(), false);            
+            if (getMaxPreparedStatements() == -1)
+            {
+                // since there is no limit, create a prepared statement pool with an 
eviction thread
+                //  evictor settings are the same as the connection pool settings.
+                stmtPool = new GenericKeyedObjectPool(null,
+                    getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
+                    getMaxIdle(), false, false,
+                    
getTimeBetweenEvictionRunsMillis(),getNumTestsPerEvictionRun(),getMinEvictableIdleTimeMillis(),
+                    false);
+            }
+            else
+            {
+                // since there is limit, create a prepared statement pool without an 
eviction thread
+                //  pool has LRU functionality so when the limit is reached, 15% of 
the pool is cleared.
+                // see 
org.apache.commons.pool.impl.GenericKeyedObjectPool.clearOldest method
+                stmtPool = new GenericKeyedObjectPool(null,
+                    getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
+                    getMaxIdle(), getMaxPreparedStatements(), false, false,
+                    -1,0,0, // -1 tells the pool that there should be no eviction 
thread.
+                    false);
+            }
         }
-        
         // Workaround for buggy WebLogic 5.1 classloader - ignore the
         // exception upon first invocation.
         try {
@@ -201,6 +217,8 @@
             String.valueOf(getNumTestsPerEvictionRun())));
         ref.add(new StringRefAddr("minEvictableIdleTimeMillis", 
             String.valueOf(getMinEvictableIdleTimeMillis())));
+        ref.add(new StringRefAddr("maxPreparedStatements",
+            String.valueOf(getMaxPreparedStatements())));
 
         return ref;
     }
@@ -275,6 +293,11 @@
                     setMinEvictableIdleTimeMillis(
                         Integer.parseInt(ra.getContent().toString()));
                 }
+                ra = ref.get("maxPreparedStatements");
+                if (ra != null && ra.getContent() != null) {
+                    setMaxPreparedStatements(
+                        Integer.parseInt(ra.getContent().toString()));
+                }
 
                 cpds = this;
             }
@@ -552,5 +575,14 @@
     public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
         assertInitializationAllowed();
         _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+    }
+    public int getMaxPreparedStatements()
+    {
+        return _maxPreparedStatements;
+    }
+
+    public void setMaxPreparedStatements(int maxPreparedStatements)
+    {
+        _maxPreparedStatements = maxPreparedStatements;
     }
 }
Index: src/test/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons/dbcp/src/test/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java,v
retrieving revision 1.7
diff -u -r1.7 TestSharedPoolDataSource.java
--- src/test/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java  28 Feb 
2004 11:47:52 -0000      1.7
+++ src/test/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java  29 Feb 
2004 00:26:55 -0000
@@ -412,7 +412,6 @@
         conn3.close();
     }     
 
-
     // Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS 
     // when setPoolPreparedStatements(true)
     public void testPoolPrepareStatement() throws Exception 
@@ -429,5 +428,80 @@
         rset.close();
         stmt.close();
         conn.close();
+    }
+
+    public void testPoolPreparedStatements() throws Exception {
+        DriverAdapterCPDS mypcds = new DriverAdapterCPDS();
+        DataSource myds = null;
+        mypcds.setDriver("org.apache.commons.dbcp.TesterDriver");
+        mypcds.setUrl("jdbc:apache:commons:testdriver");
+        mypcds.setUser("foo");
+        mypcds.setPassword("bar");
+        mypcds.setPoolPreparedStatements(true);
+        mypcds.setMaxPreparedStatements(10);
+
+        SharedPoolDataSource tds = new SharedPoolDataSource();
+        tds.setConnectionPoolDataSource(mypcds);
+        tds.setMaxActive(getMaxActive());
+        tds.setMaxWait((int)(getMaxWait()));
+        tds.setDefaultTransactionIsolation(
+            Connection.TRANSACTION_READ_COMMITTED);
+
+        myds = tds;
+
+        Connection conn = ds.getConnection();
+        PreparedStatement stmt = null;
+        ResultSet rset = null;
+
+        assertTrue(null != conn);
+
+        stmt = conn.prepareStatement("select * from dual");
+        assertTrue(null != stmt);
+        long l1HashCode = stmt.hashCode();
+        rset = stmt.executeQuery();
+        assertTrue(null != rset);
+        assertTrue(rset.next());
+        rset.close();
+        stmt.close();
+
+        stmt = conn.prepareStatement("select * from dual");
+        assertTrue(null != stmt);
+        long l2HashCode = stmt.hashCode();
+        rset = stmt.executeQuery();
+        assertTrue(null != rset);
+        assertTrue(rset.next());
+        rset.close();
+        stmt.close();
+
+        // statement pooling is not enabled, we should get different statements
+        assertTrue(l1HashCode != l2HashCode);
+        conn.close();
+        conn = null;
+
+        conn = myds.getConnection();
+
+        stmt = conn.prepareStatement("select * from dual");
+        assertTrue(null != stmt);
+        long l3HashCode = stmt.hashCode();
+        rset = stmt.executeQuery();
+        assertTrue(null != rset);
+        assertTrue(rset.next());
+        rset.close();
+        stmt.close();
+
+        stmt = conn.prepareStatement("select * from dual");
+        assertTrue(null != stmt);
+        long l4HashCode = stmt.hashCode();
+        rset = stmt.executeQuery();
+        assertTrue(null != rset);
+        assertTrue(rset.next());
+        rset.close();
+        stmt.close();
+
+        // prepared statement pooling is working
+        assertTrue(l3HashCode == l4HashCode);
+        conn.close();
+        conn = null;
+
     }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to