Author: sandymac Date: Sat Mar 25 23:13:22 2006 New Revision: 388880 URL: http://svn.apache.org/viewcvs?rev=388880&view=rev Log: Detect when making new objects is relatively slow or expensive. When it is, try to anticipate demand and create idle objects while allowing concurrent pool access. The idea came from a discussion with Peter Steijn on ways to reduce pool latency.
Modified: jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/CompositeObjectPool.java jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/GrowManager.java Modified: jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/CompositeObjectPool.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/CompositeObjectPool.java?rev=388880&r1=388879&r2=388880&view=diff ============================================================================== --- jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/CompositeObjectPool.java (original) +++ jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/CompositeObjectPool.java Sat Mar 25 23:13:22 2006 @@ -179,10 +179,10 @@ */ public void addObject() throws Exception { assertOpen(); + final Object obj = factory.makeObject(); + factory.passivateObject(obj); synchronized (pool) { - final Object obj = factory.makeObject(); - factory.passivateObject(obj); - // if the pool is closed, discard returned objects + // if the pool was closed between the asserOpen and the synchronize then discard returned objects if (isOpen()) { manager.returnToPool(obj); } else { Modified: jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/GrowManager.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/GrowManager.java?rev=388880&r1=388879&r2=388880&view=diff ============================================================================== --- jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/GrowManager.java (original) +++ jakarta/commons/proper/pool/trunk/src/java/org/apache/commons/pool/composite/GrowManager.java Sat Mar 25 23:13:22 2006 @@ -19,6 +19,8 @@ import org.apache.commons.pool.PoolableObjectFactory; import java.io.Serializable; +import java.util.TimerTask; +import java.util.Timer; /** * Grows the pool automatically when it is exhausted. @@ -33,6 +35,11 @@ final class GrowManager extends AbstractManager implements Serializable { private static final long serialVersionUID = 1225746308358794900L; + private static final Timer PREFILL_TIMER = CompositeObjectPool.COMPOSITE_TIMER; + + private final Object avgLock = new Object(); + private long activateAvg = 0; + private long makeAvg = 0; /** * Retreives the next object from the pool, creating new objects if the pool has been exhausted. @@ -43,6 +50,8 @@ public Object nextFromPool() throws Exception { assert Thread.holdsLock(objectPool.getPool()); Object obj = null; + + final long startActivateTime = System.currentTimeMillis(); // Drain until good or empty while (objectPool.getLender().size() > 0 && obj == null) { obj = objectPool.getLender().borrow(); @@ -65,10 +74,15 @@ } } } - - if (obj == null) { + if (obj != null) { + updateActivateTimings(startActivateTime, System.currentTimeMillis()); + } else { + final long startMakeTime = System.currentTimeMillis(); obj = objectPool.getFactory().makeObject(); + updateMakeTimings(startMakeTime, System.currentTimeMillis()); } + + schedulePrefill(); return obj; } @@ -93,7 +107,74 @@ return obj; } + /** + * Update the moving average of how long it takes to activate and validate idle objects. + * @param start start of activation and validation + * @param end end of activation and validation + */ + private void updateActivateTimings(final long start, final long end) { + final long elapsed = end - start; + if (elapsed > 0L) { + synchronized (avgLock) { + activateAvg = (activateAvg * 9L + elapsed) / 10L; + } + } + } + + /** + * Update the moving average of how long it takes to make a new objects. + * @param start start of makeObject + * @param end end of makeObject + */ + private void updateMakeTimings(final long start, final long end) { + final long elapsed = end - start; + if (elapsed > 0L) { + synchronized (avgLock) { + makeAvg = (makeAvg * 9L + elapsed) / 10L; + } + } + } + + /** + * Does [EMAIL PROTECTED] PoolableObjectFactory#makeObject} take a relativly long time compared to + * [EMAIL PROTECTED] PoolableObjectFactory#activateObject} and [EMAIL PROTECTED] PoolableObjectFactory#validateObject}. + * @return <code>true</code> if [EMAIL PROTECTED] PoolableObjectFactory#makeObject} takes a long time. + */ + private boolean isMakeObjectExpensive() { + synchronized (avgLock) { + // XXX: This is a guess at an optimal balance. + // Considering makeObject to be expensive if it takes 3 times longer than activation takes. + // That is based on a benchmark by Peter Steijn for database connection pooling. + return activateAvg > 0L && activateAvg * 3L < makeAvg; + } + } + + /** + * Schedule a <code>PrefillTask</code> if the idle object pool is empty + * and <code>makeObject</code> is relatively expensive. + */ + private void schedulePrefill() { + if (objectPool.getLender().size() == 0 && isMakeObjectExpensive()) { + PREFILL_TIMER.schedule(new PrefillTask(), 0L); + } + } + public String toString() { - return "GrowManager{}"; + return "GrowManager{makeObjectExpensive=" + isMakeObjectExpensive() + "}"; + } + + /** + * A <code>TimerTask</code> that will add another object if the pool is empty. + */ + private class PrefillTask extends TimerTask { + public void run() { + try { + if (objectPool.getNumIdle() == 0) { + objectPool.addObject(); + } + } catch (Exception e) { + // swallowed + } + } } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]