You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ps...@apache.org on 2014/05/31 02:54:27 UTC

svn commit: r1598794 - in /commons/proper/pool/trunk/src: changes/ main/java/org/apache/commons/pool2/impl/ test/java/org/apache/commons/pool2/impl/

Author: psteitz
Date: Sat May 31 00:54:27 2014
New Revision: 1598794

URL: http://svn.apache.org/r1598794
Log:
Made fairness configurable for GOP, GKOP.  Also made 'testBorrowObjectFairness' more rigorous (was succeeding despite 2.x pools not being fair).  JIRA: POOL-262.

Modified:
    commons/proper/pool/trunk/src/changes/changes.xml
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseGenericObjectPool.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseObjectPoolConfig.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolMXBean.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPoolMXBean.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/InterruptibleReentrantLock.java
    commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/LinkedBlockingDeque.java
    commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
    commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java

Modified: commons/proper/pool/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/changes/changes.xml?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/changes/changes.xml (original)
+++ commons/proper/pool/trunk/src/changes/changes.xml Sat May 31 00:54:27 2014
@@ -45,6 +45,9 @@ The <action> type attribute can be add,u
   <body>
   <release version="2.3" date="TBD" description=
 "TBD">
+    <action dev="psteitz" type="add" issue="POOL-262">
+      Made fairness configurable for GenericObjectPool, GenericKeyedObjectPool.
+    </action>
     <action dev="markt" type="fix">
       Improve performance of statistics collection for pools that extend
       BaseGenericObjectPool.

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseGenericObjectPool.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseGenericObjectPool.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseGenericObjectPool.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseGenericObjectPool.java Sat May 31 00:54:27 2014
@@ -65,6 +65,7 @@ public abstract class BaseGenericObjectP
     private volatile long maxWaitMillis =
             BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;
     private volatile boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO;
+    private final boolean fairness;
     private volatile boolean testOnCreate =
             BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
     private volatile boolean testOnBorrow =
@@ -137,6 +138,7 @@ public abstract class BaseGenericObjectP
 
         // save the current CCL to be used later by the evictor Thread
         factoryClassLoader = Thread.currentThread().getContextClassLoader();
+        fairness = config.getFairness();
     }
 
 
@@ -249,6 +251,17 @@ public abstract class BaseGenericObjectP
     public final boolean getLifo() {
         return lifo;
     }
+    
+    /**
+     * Returns whether or not the pool serves threads waiting to borrow objects fairly.
+     * True means that waiting threads are served as if waiting in a FIFO queue.
+     *
+     * @return <code>true</code> if waiting threads are to be served
+     *             by the pool in arrival order
+     */
+    public final boolean getFairness() {
+        return fairness;
+    }
 
     /**
      * Sets whether the pool has LIFO (last in, first out) behaviour with

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseObjectPoolConfig.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseObjectPoolConfig.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseObjectPoolConfig.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/BaseObjectPoolConfig.java Sat May 31 00:54:27 2014
@@ -35,6 +35,13 @@ public abstract class BaseObjectPoolConf
      * @see GenericKeyedObjectPool#getLifo()
      */
     public static final boolean DEFAULT_LIFO = true;
+    
+    /**
+     * The default value for the {@code fairness} configuration attribute.
+     * @see GenericObjectPool#getFairness()
+     * @see GenericKeyedObjectPool#getFairness()
+     */
+    public static final boolean DEFAULT_FAIRNESS = false;
 
     /**
      * The default value for the {@code maxWait} configuration attribute.
@@ -148,6 +155,8 @@ public abstract class BaseObjectPoolConf
 
 
     private boolean lifo = DEFAULT_LIFO;
+    
+    private boolean fairness = DEFAULT_FAIRNESS;
 
     private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
 
@@ -196,6 +205,20 @@ public abstract class BaseObjectPoolConf
     public boolean getLifo() {
         return lifo;
     }
+    
+    /**
+     * Get the value for the {@code fairness} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @return  The current setting of {@code fairness} for this configuration
+     *          instance
+     *
+     * @see GenericObjectPool#getFairness()
+     * @see GenericKeyedObjectPool#getFairness()
+     */
+    public boolean getFairness() {
+        return fairness;
+    }
 
     /**
      * Set the value for the {@code lifo} configuration attribute for pools
@@ -210,6 +233,20 @@ public abstract class BaseObjectPoolConf
     public void setLifo(boolean lifo) {
         this.lifo = lifo;
     }
+    
+    /**
+     * Set the value for the {@code fairness} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @param fairness The new setting of {@code fairness}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getFairness()
+     * @see GenericKeyedObjectPool#getFairness()
+     */
+    public void setFairness(boolean fairness) {
+        this.fairness = fairness;
+    }
 
     /**
      * Get the value for the {@code maxWait} configuration attribute for pools

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java Sat May 31 00:54:27 2014
@@ -106,6 +106,7 @@ public class GenericKeyedObjectPool<K,T>
             throw new IllegalArgumentException("factory may not be null");
         }
         this.factory = factory;
+        this.fairness = config.getFairness();
 
         setConfig(config);
 
@@ -1082,7 +1083,7 @@ public class GenericKeyedObjectPool<K,T>
                 lock.lock();
                 objectDeque = poolMap.get(k);
                 if (objectDeque == null) {
-                    objectDeque = new ObjectDeque<T>();
+                    objectDeque = new ObjectDeque<T>(fairness);
                     objectDeque.getNumInterested().incrementAndGet();
                     // NOTE: Keys must always be added to both poolMap and
                     //       poolKeyList at the same time while protected by
@@ -1398,8 +1399,7 @@ public class GenericKeyedObjectPool<K,T>
      */
     private class ObjectDeque<S> {
 
-        private final LinkedBlockingDeque<PooledObject<S>> idleObjects =
-                new LinkedBlockingDeque<PooledObject<S>>();
+        private final LinkedBlockingDeque<PooledObject<S>> idleObjects;
 
         /*
          * Number of instances created - number destroyed.
@@ -1424,6 +1424,16 @@ public class GenericKeyedObjectPool<K,T>
         private final AtomicLong numInterested = new AtomicLong(0);
 
         /**
+         * Create a new ObjecDeque with the given fairness policy.
+         * @param fairness true means client threads waiting to borrow / return instances
+         * will be served as if waiting in a FIFO queue.
+         */
+        public ObjectDeque(boolean fairness) {
+            idleObjects = new LinkedBlockingDeque<PooledObject<S>>(fairness);
+            
+        }
+        
+        /**
          * Obtain the idle objects for the current key.
          *
          * @return The idle objects
@@ -1469,6 +1479,7 @@ public class GenericKeyedObjectPool<K,T>
     private volatile int maxTotalPerKey =
         GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
     private final KeyedPooledObjectFactory<K,T> factory;
+    private final boolean fairness;
 
 
     //--- internal attributes --------------------------------------------------

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolMXBean.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolMXBean.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolMXBean.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolMXBean.java Sat May 31 00:54:27 2014
@@ -43,6 +43,11 @@ public interface GenericKeyedObjectPoolM
      */
     boolean getBlockWhenExhausted();
     /**
+     * See {@link GenericKeyedObjectPool#getFairness()}
+     * @return See {@link GenericKeyedObjectPool#getFairness()}
+     */
+    boolean getFairness();
+    /**
      * See {@link GenericKeyedObjectPool#getLifo()}
      * @return See {@link GenericKeyedObjectPool#getLifo()}
      */

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java Sat May 31 00:54:27 2014
@@ -111,6 +111,8 @@ public class GenericObjectPool<T> extend
             throw new IllegalArgumentException("factory may not be null");
         }
         this.factory = factory;
+        
+        idleObjects = new LinkedBlockingDeque<PooledObject<T>>(config.getFairness());
 
         setConfig(config);
 
@@ -1096,8 +1098,7 @@ public class GenericObjectPool<T> extend
      * {@link #_maxActive} objects created at any one time.
      */
     private final AtomicLong createCount = new AtomicLong(0);
-    private final LinkedBlockingDeque<PooledObject<T>> idleObjects =
-        new LinkedBlockingDeque<PooledObject<T>>();
+    private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
 
     // JMX specific attributes
     private static final String ONAME_BASE =

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPoolMXBean.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPoolMXBean.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPoolMXBean.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/GenericObjectPoolMXBean.java Sat May 31 00:54:27 2014
@@ -43,6 +43,11 @@ public interface GenericObjectPoolMXBean
      * See {@link GenericObjectPool#getLifo()}
      * @return See {@link GenericObjectPool#getLifo()}
      */
+    boolean getFairness();  
+    /**
+     * See {@link GenericObjectPool#getFairness()}
+     * @return See {@link GenericObjectPool#getFairness()}
+     */
     boolean getLifo();
     /**
      * See {@link GenericObjectPool#getMaxIdle()}

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/InterruptibleReentrantLock.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/InterruptibleReentrantLock.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/InterruptibleReentrantLock.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/InterruptibleReentrantLock.java Sat May 31 00:54:27 2014
@@ -34,6 +34,16 @@ class InterruptibleReentrantLock extends
     private static final long serialVersionUID = 1L;
 
     /**
+     * Create a new InterruptibleReentrantLock with the given fairness policy.
+     * 
+     * @param fairness true means threads should acquire contended locks as if
+     * waiting in a FIFO queue
+     */
+    public InterruptibleReentrantLock(boolean fairness) {
+        super(fairness);
+    }
+
+    /**
      * Interrupt the threads that are waiting on a specific condition
      *
      * @param condition the condition on which the threads are waiting.

Modified: commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/LinkedBlockingDeque.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/LinkedBlockingDeque.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/LinkedBlockingDeque.java (original)
+++ commons/proper/pool/trunk/src/main/java/org/apache/commons/pool2/impl/LinkedBlockingDeque.java Sat May 31 00:54:27 2014
@@ -147,14 +147,13 @@ class LinkedBlockingDeque<E> extends Abs
     private final int capacity;
 
     /** Main lock guarding all access */
-    private final InterruptibleReentrantLock lock =
-            new InterruptibleReentrantLock();
+    private final InterruptibleReentrantLock lock;
 
     /** Condition for waiting takes */
-    private final Condition notEmpty = lock.newCondition();
+    private final Condition notEmpty;
 
     /** Condition for waiting puts */
-    private final Condition notFull = lock.newCondition();
+    private final Condition notFull;
 
     /**
      * Creates a {@code LinkedBlockingDeque} with a capacity of
@@ -163,6 +162,16 @@ class LinkedBlockingDeque<E> extends Abs
     public LinkedBlockingDeque() {
         this(Integer.MAX_VALUE);
     }
+    
+    /**
+     * Creates a {@code LinkedBlockingDeque} with a capacity of
+     * {@link Integer#MAX_VALUE} and the given fairness policy.
+     * @param fairness true means threads waiting on the deque should be served
+     * as if waiting in a FIFO request queue
+     */
+    public LinkedBlockingDeque(boolean fairness) {
+        this(Integer.MAX_VALUE, fairness);
+    }
 
     /**
      * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
@@ -171,8 +180,24 @@ class LinkedBlockingDeque<E> extends Abs
      * @throws IllegalArgumentException if {@code capacity} is less than 1
      */
     public LinkedBlockingDeque(int capacity) {
+        this(capacity, false);
+    }
+    
+    /**
+     * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity
+     * and fairness policy.
+     *
+     * @param capacity the capacity of this deque
+     * @param fairness true means threads waiting on the deque should be served
+     * as if waiting in a FIFO request queue
+     * @throws IllegalArgumentException if {@code capacity} is less than 1
+     */
+    public LinkedBlockingDeque(int capacity, boolean fairness) {
         if (capacity <= 0) throw new IllegalArgumentException();
         this.capacity = capacity;
+        lock = new InterruptibleReentrantLock(fairness);
+        notEmpty = lock.newCondition();
+        notFull = lock.newCondition();
     }
 
     /**

Modified: commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java (original)
+++ commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java Sat May 31 00:54:27 2014
@@ -1029,6 +1029,68 @@ public class TestGenericKeyedObjectPool 
             }
         }
     }
+    
+    /*
+     * Note: This test relies on timing for correct execution. There *should* be
+     * enough margin for this to work correctly on most (all?) systems but be
+     * aware of this if you see a failure of this test.
+     */
+    @SuppressWarnings({
+        "rawtypes", "unchecked"
+    })
+    @Test(timeout=60000)
+    public void testBorrowObjectFairness() throws Exception {
+        
+        int numThreads = 40;
+        int maxTotal = 40;
+
+        GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
+        config.setMaxTotalPerKey(maxTotal);
+        config.setFairness(true);
+        config.setLifo(false);
+        config.setMaxIdlePerKey(maxTotal);
+        
+        pool = new GenericKeyedObjectPool<String, String>(factory, config);
+        
+        // Exhaust the pool
+        String[] objects = new String[maxTotal];
+        for (int i = 0; i < maxTotal; i++) {
+            objects[i] = pool.borrowObject("0");
+        }
+
+        // Start and park threads waiting to borrow objects
+        TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            threads[i] = new TestThread(pool, 1, 0, 2000, false, "0" + String.valueOf(i % maxTotal), "0");
+            Thread t = new Thread(threads[i]);
+            t.start();
+            // Short delay to ensure threads start in correct order
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException e) {
+                fail(e.toString());
+            }
+        }
+        
+        // Return objects, other threads should get served in order
+        for (int i = 0; i < maxTotal; i++) {
+            pool.returnObject("0", objects[i]);
+        }
+
+        // Wait for threads to finish
+        for(int i=0;i<numThreads;i++) {
+            while(!(threads[i]).complete()) {
+                try {
+                    Thread.sleep(500L);
+                } catch(InterruptedException e) {
+                    // ignored
+                }
+            }
+            if(threads[i].failed()) {
+                fail("Thread "+i+" failed: "+threads[i]._exception.toString());
+            }
+        }
+    }
 
     @Test(timeout=60000)
     public void testConstructors() throws Exception {
@@ -1603,27 +1665,47 @@ public class TestGenericKeyedObjectPool 
     static class TestThread<T> implements Runnable {
         private final java.util.Random _random = new java.util.Random();
 
-        // Thread config items
+        /** GKOP to hit */
         private final KeyedObjectPool<String,T> _pool;
+        /** number of borrow/return iterations */
         private final int _iter;
-        private final int _delay;
+        /** delay before borrow */
+        private final int _startDelay;
+        /** delay before return */
+        private final int _holdTime;
+        /** whether or not delays are random (with max = configured values) */
+        private final boolean _randomDelay;
+        /** expected object */
+        private final T _expectedObject;
+        /** key used in borrow / return sequence - null means random */
+        private final String _key;
 
         private volatile boolean _complete = false;
         private volatile boolean _failed = false;
         private volatile Exception _exception;
 
         public TestThread(KeyedObjectPool<String,T> pool) {
-            this(pool, 100, 50);
+            this(pool, 100, 50, 50, true, null, null);
         }
 
         public TestThread(KeyedObjectPool<String,T> pool, int iter) {
-            this(pool, iter, 50);
+            this(pool, iter, 50, 50, true, null, null);
         }
-
+        
         public TestThread(KeyedObjectPool<String,T> pool, int iter, int delay) {
+            this(pool, iter, delay, delay, true, null, null);
+        }
+
+        public TestThread(KeyedObjectPool<String,T> pool, int iter, int startDelay,
+            int holdTime, boolean randomDelay, T expectedObject, String key) {
             _pool = pool;
             _iter = iter;
-            _delay = delay;
+            _startDelay = startDelay;
+            _holdTime = holdTime;
+            _randomDelay = randomDelay;
+            _expectedObject = expectedObject;
+            _key = key;
+            
         }
 
         public boolean complete() {
@@ -1637,9 +1719,9 @@ public class TestGenericKeyedObjectPool 
         @Override
         public void run() {
             for(int i=0;i<_iter;i++) {
-                String key = String.valueOf(_random.nextInt(3));
+                String key = _key == null ? String.valueOf(_random.nextInt(3)) : _key;
                 try {
-                    Thread.sleep(_random.nextInt(_delay));
+                    Thread.sleep(_randomDelay ? _random.nextInt(_startDelay) : _startDelay);
                 } catch(InterruptedException e) {
                     // ignored
                 }
@@ -1652,9 +1734,16 @@ public class TestGenericKeyedObjectPool 
                     _complete = true;
                     break;
                 }
+                
+                if (_expectedObject != null && !_expectedObject.equals(obj)) {
+                    _exception = new Exception("Expected: "+_expectedObject+ " found: "+obj);
+                    _failed = true;
+                    _complete = true;
+                    break;
+                }
 
                 try {
-                    Thread.sleep(_random.nextInt(_delay));
+                    Thread.sleep(_randomDelay ? _random.nextInt(_holdTime) : _holdTime);
                 } catch(InterruptedException e) {
                     // ignored
                 }

Modified: commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
URL: http://svn.apache.org/viewvc/commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java?rev=1598794&r1=1598793&r2=1598794&view=diff
==============================================================================
--- commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java (original)
+++ commons/proper/pool/trunk/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java Sat May 31 00:54:27 2014
@@ -1917,8 +1917,8 @@ public class TestGenericObjectPool exten
     /**
      * @param validateLatency the validateLatency to set
      */
-    public void setDelayLatency(long delayLatency) {
-        this.validateLatency = delayLatency;
+    public void setValidateLatency(long validateLatency) {
+        this.validateLatency = validateLatency;
     }
 }
 
@@ -1941,29 +1941,43 @@ public class TestGenericObjectPool exten
         "rawtypes", "unchecked"
     })
     @Test(timeout=60000)
-    public void testBorrowObjectFairness() {
+    public void testBorrowObjectFairness() throws Exception {
+        
+        int numThreads = 40;
+        int maxTotal = 40;
 
-        // Config
-        int numThreads = 30;
-        int maxTotal = 10;
-
-        pool.setMaxTotal(maxTotal);
-        pool.setBlockWhenExhausted(true);
-        pool.setTimeBetweenEvictionRunsMillis(-1);
+        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
+        config.setMaxTotal(maxTotal);
+        config.setMaxIdle(maxTotal);
+        config.setFairness(true);
+        config.setLifo(false);
+        
+        pool = new GenericObjectPool(factory, config);
+        
+        // Exhaust the pool
+        String[] objects = new String[maxTotal];
+        for (int i = 0; i < maxTotal; i++) {
+            objects[i] = pool.borrowObject();
+        }
 
-        // Start threads to borrow objects
+        // Start and park threads waiting to borrow objects
         TestThread[] threads = new TestThread[numThreads];
         for(int i=0;i<numThreads;i++) {
-            threads[i] = new TestThread(pool, 1, 2000, false, String.valueOf(i % maxTotal));
+            threads[i] = new TestThread(pool, 1, 0, 2000, false, String.valueOf(i % maxTotal));
             Thread t = new Thread(threads[i]);
             t.start();
             // Short delay to ensure threads start in correct order
             try {
-                Thread.sleep(50);
+                Thread.sleep(10);
             } catch (InterruptedException e) {
                 fail(e.toString());
             }
         }
+        
+        // Return objects, other threads should get served in order
+        for (int i = 0; i < maxTotal; i++) {
+            pool.returnObject(objects[i]);
+        }
 
         // Wait for threads to finish
         for(int i=0;i<numThreads;i++) {