Terry explains the issue well so take a look at his message for a
description of the problem.

This patch fixes the problem by keeping track of the number of threads
that are in the wait state.  If a new thread calls the
getInternalPooledConnection method and the wait count is not zero then
the calling thread drops into the wait state. This allows a potential
thread "awakened" from the wait state by a notify call to acquire the
synchronize lock and obtain the connection.  It also acts in a "fair"
way since if there are existing threads in a wait state when a thread
calls the get connection method then the thread will also go into the
wait state, essentially getting in the back of the line.

Byron  


On Tue, 2001-11-27 at 12:50, Terry August wrote:
> 
> I have ran into a problem with the ConnectionPool class from Turbine 2.x.  I 
> noticed the problem while making many requests to a pool configured with a 
> maximum of 5 connections.  What happens is the following:
> 
> 1.  All 5 connections go into use.
> 2.  Several threads are waiting for a connection to be returned to the 
> stack.
> 3.  1 connection is returned and notify() is called - line 655
> 4.  One of the threads is awakened and is now actively competing for the 
> lock.
> 5.  A new thread (non-waiting) is created and gets the lock before the 
> awakened thread.
> 6.  The new thread takes the newly available connection from the stack.
> 7.  The awakened thread now gets the lock and discovers that there is no 
> available connection in the pool even though it was awakened.
> 8.  The code in lines 580-585 are now executed and an exception is thrown.
> 
> The problem is that this code will result in exceptions all the time on 
> heavily hit dynamic pages (with several connections being made in the page) 
> unless the maxConnections is raised unjustly.  Also connectionWaitTimeout 
> specifies the amount of time a thread will wait, but doesn't consider the 
> fact that the above sequence might occur.  In this case the awakened thread 
> may have only waited 1 second and caused an error, even if 
> connectionWaitTimeout is set to 10 seconds.  I can see two solutions.  One 
> solution is to revamp the synchronization to disallow new threads from 
> taking the lock before the awakened thread gets the lock.  Or, the 
> connectionWaitTimeout property can be considered a TOTAL wait time and the 
> code can loop in that section.  Something like the following may work:
> 

? idbroker.patch2.txt
? transkey.patch.txt
? con_pool.patch.txt
Index: src/java/org/apache/torque/pool/ConnectionPool.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-turbine-torque/src/java/org/apache/torque/pool/ConnectionPool.java,v
retrieving revision 1.5
diff -u -r1.5 ConnectionPool.java
--- src/java/org/apache/torque/pool/ConnectionPool.java 7 Dec 2001 16:13:10 -0000      
 1.5
+++ src/java/org/apache/torque/pool/ConnectionPool.java 17 Jan 2002 03:46:25 -0000
@@ -488,6 +488,13 @@
                             "connection from empty pool!");
     }
 
+
+    /**
+     * Counter that keeps track of the number of threads that are in
+     * the wait state, waiting to aquire a connection.
+     */
+    private static int waitCount = 0;
+  
     /**
      * Gets a pooled database connection.
      *
@@ -498,10 +505,21 @@
     private synchronized DBConnection getInternalPooledConnection()
         throws ConnectionWaitTimeoutException, Exception
     {
-        DBConnection dbconn = null;
-
-        if ( pool.empty() )
+        if ( waitCount > 0 || pool.empty() )
         {
+            // We test waitCount > 0 to make sure no other threads are
+            // waiting for a connection. If waitCount is not tested
+            // the following situation occurs: After a thread
+            // returns a connection it calls notify. If another thread
+            // is currently in the wait state, it will awaken and
+            // begin competing for the synchronization lock.
+            // Meanwhile, if another thread is attempting to call this
+            // method, it may acquire the lock first and "steal" the
+            // connection.  The thread that was waiting will then
+            // acquire the lock, but the connection is no longer
+            // available, so a ConnectionWaitTimeoutExecption is
+            // thrown.
+          
             connectionAttemptsCounter++;
 
             // The connection pool is empty and we cannot allocate any new
@@ -509,12 +527,18 @@
             // a connection is returned.
             try
             {
+                waitCount++;
                 wait( connectionWaitTimeout );
             }
             catch (InterruptedException ignored)
             {
                 // Don't care how we come out of the wait state.
             }
+            finally
+            {
+                waitCount--;
+            }
+            
 
             // Check for a returned connection.
             if ( pool.empty() )
@@ -523,14 +547,9 @@
                 // someone returning a connection.
                 throw new ConnectionWaitTimeoutException(url);
             }
-            dbconn = popConnection();
-        }
-        else
-        {
-            dbconn = popConnection();
         }
 
-        return dbconn;
+        return popConnection();
     }
 
     /**

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

Reply via email to