package net.spy.pool; import java.util.ArrayList; import java.util.Iterator; import net.spy.SpyObject; import net.spy.util.SpyConfig; /** * PoolContainer is the storage for a given pool. */ public class PoolContainer extends SpyObject { private static final int MAX_RETRIES=6; // Amount of time to wait for a connection to become available. private static final int AVAILABILITY_WAIT=500; // The minimum allowable value for maximum age. private static final int MIN_MAX_AGE=5000; // Default maximum number of objects for a pool private static final int DEFAULT_MAX_OBJECTS=5; private static final int DEFAULT_YELLOW_LINE=75; private static final float PERCENT=100.0f; private static final int PING_ON_CHECKOUT=1; // Buffer length for stringification private static final int TOSTRING_LEN=256; private ArrayList<PoolAble> pool=null; private SpyConfig conf=null; private String name=null; private PoolFiller filler=null; private int minObjects=-1; private int initObjects=-1; private int maxObjects=-1; private long maxAge=0; // The percentage at which we start making people wait before giving // them new connections. private int yellowLine=-1; private int pingConfig=0; private static int objectId=0; /** * Create a new PoolContainer for a pool with a given name, and filler. * * The following optional config parameters will be used: * <ul> * <li><poolname>.min - minimum number of items in the pool</li> * <li><poolname>.start - initial number of objects in the pool</li> * <li><poolname>.yellow - when the pool is this percent full, * we hesitate more before giving out connections.</li> * <li><poolname>.max - maximum number of items in the pool</li> * <li><poolname>.pingOnCheckout - if true, ping on checkout (true) * </li> * </ul> * * @param nm name of the pool * @param pf the PoolFiller to use * @param cnf a SpyConfig object that should describe the pool * parameters. * * @exception PoolException when something bad happens */ public PoolContainer(String nm, PoolFiller pf, SpyConfig cnf) throws PoolException { super(); this.conf=cnf; this.name=nm; this.filler=pf; initialize(); } /** * Create a new PoolContainer for a pool with a given name, and filler. * * The following optional config parameters will be used: * <ul> * <li><poolname>.min - minimum number of items in the pool</li> * <li><poolname>.start - initial number of objects in the pool</li> * <li><poolname>.yellow - when the pool is this percent full, * we hesitate more before giving out connections.</li> * <li><poolname>.max - maximum number of items in the pool</li> * <li><poolname>.pingOnCheckout - if true, ping on checkout (true) * </li> * </ul> * * @param nm name of the pool * @param pf the PoolFiller to use * * @exception PoolException when something bad happens */ public PoolContainer(String nm, PoolFiller pf) throws PoolException { this(nm, pf, pf.getConfig()); } /** * Get the name of the pool. */ public String getName() { return(name); } // Conditionally ping if this is a good time. Just return true if this // isn't a good time to ping private boolean checkAlive(PoolAble p, int when) { boolean rv=true; if( (pingConfig & when) != 0) { rv=p.isAlive(); } return(rv); } /** * Get an object from the pool. It could take up to about three * seconds to get an object from the pool. * * @exception PoolException when something bad happens */ public PooledObject getObject() throws PoolException { PooledObject rv=null; PoolAble poolable=null; // How many times we're flipping through the object pool int retries=MAX_RETRIES; // Synchronize on the pool object. synchronized(pool) { // We'll try up to three seconds to get an object from the pool for(int retry=0; poolable==null && retry<retries; retry++) { // Find the next available object. for(PoolAble p : pool) { // If it's not checked out, and it works, we have our man! if(p.isAvailable() && checkAlive(p, PING_ON_CHECKOUT)) { // Since we got one from the pool, we want to move it // to the end of the vector. poolable=p; break; } } // Flipping through the current pool // If we didn't get anything, and we're not at least // to our yellow line, open a new connection if(poolable==null && totalObjects()<yellowLine) { poolable=getNewObject(); } // If we didn't get anything, deal with that situation. if(poolable==null) { try { getLogger().debug("No free entries in pool, sleeping"); // We're halfway through, or more! Desperate measures! if(retry==retries/2) { getLogger().debug("Trying to force cleanup!"); GarbageCollector gc= GarbageCollector.getGarbageCollector(); gc.collect(); } // Wait a half a second if the pool is full, in case // something gets checked in Thread.sleep(AVAILABILITY_WAIT); } catch(InterruptedException e) { getLogger().debug("Interrupted"); } } }// Retries for an object in the existing pool. // Check it out right now. if(poolable!=null) { rv=new PooledObject(poolable); } } // End of pool synchronization // If the above didn't get us an object, we'll resort to getting a // new one. if(rv==null) { // OK, got nothing from the pool, in a desperate attempt, we'll // be grabbing a new object. poolable=getNewObject(); rv=new PooledObject(poolable); } // OK, let's stick it at the end of the vector (may already be, but // you know...) so that it's one of the last we check for next time. // Hold it still whlie we do this... synchronized(pool) { getLogger().debug("Moving %s", poolable); pool.remove(poolable); pool.add(poolable); } return(rv); } // Name to print in debuggy type things. private String debugName() { return(name + " @" + Integer.toHexString(hashCode())); } /** * debugging tool, dump out the current state of the pool */ @Override public String toString() { StringBuilder sb=new StringBuilder(TOSTRING_LEN); sb.append("Pool "); sb.append(debugName()); sb.append(" - total Objects: "); sb.append(totalObjects()); sb.append(", available objects: "); sb.append(availableObjects()); sb.append('\n'); synchronized (pool) { for(PoolAble p : pool) { sb.append(" "); sb.append(p); sb.append("\n"); } } return(sb.toString()); } /** * Find out how many objects are available in this pool. * * @return the number of available (not checked out) objects. */ public int availableObjects() { int ret=0; synchronized (pool) { for(PoolAble p : pool) { if(p.isAvailable()) { ret++; } } } return(ret); } /** * Remove any object that is not checked out, as long as we stay above * our minimum object requirement. * <p> * This method should only be called from the ObjectPoolCleaner -- * please don't call it directly. * * @exception PoolException when something bad happens */ void prune() throws PoolException { getLogger().debug("Beginning prune."); synchronized (pool) { // Get rid of expired things for(Iterator<PoolAble> it=pool.iterator(); it.hasNext();) { PoolAble p=it.next(); if(p.pruneStatus()>=PoolAble.MUST_CLEAN) { // Tell it that it can go away now. getLogger().debug("Removing " + p); p.discard(); it.remove(); } else if(!p.isAlive()) { // If this poolable isn't alive, remove it p.discard(); it.remove(); } } // If we don't have enough objects, go get more! They're cheap! if(totalObjects()<minObjects) { getMinObjects(); } } // pool lock } private void initialize() throws PoolException { pool=new ArrayList<PoolAble>(); // Get the min and max args. minObjects=getPropertyInt("min", 0); initObjects=getPropertyInt("start", minObjects); maxObjects=getPropertyInt("max", DEFAULT_MAX_OBJECTS); // The yellow line is the number of connections before we start to // slow it down... yellowLine=(int)((float)maxObjects * (float)getPropertyInt("yellow_line", DEFAULT_YELLOW_LINE)/PERCENT); // Set up the max age maxAge=getPropertyInt("max_age", 0); // Set the hashcode of this pool for consistent debug output. filler.setPoolHash(hashCode()); getLogger().debug("Pool %s wants a min %s, max %s and yellow-line %s", debugName(), minObjects, maxObjects, yellowLine); if(getPropertyBool("pingOnCheckout", true)) { pingConfig |= PING_ON_CHECKOUT; } try { getStartObjects(); } catch(PoolException e) { // If there was a problem initializing the pool, throw away // what we've got. for(PoolAble p : pool) { p.discard(); } throw e; } } // Populate with the minimum number of objects. private void getMinObjects() throws PoolException{ getLogger().debug("Pool %s wants at least %s object", name, minObjects); for(int i=totalObjects(); i<minObjects; i++) { getNewObject(); } } // Populate with the number of objects we need at start. private void getStartObjects() throws PoolException{ getLogger().debug("Pool %s starting with %s objects", name, initObjects); for(int i=totalObjects(); i<initObjects; i++) { getNewObject(); } } // Fetch a new object from the poolfiller, the pool is exhausted and we // need more objects. private PoolAble getNewObject() throws PoolException { PoolAble po=null; // First, if we're at capacity, do a prune and see if we can shrink // it down a bit. if(totalObjects()>=maxObjects) { prune(); } // Don't add an object if we're at capacity. if(totalObjects()<maxObjects) { getLogger().debug( "*** Getting a new object in the %s pool, have %s/%s", name, totalObjects(), maxObjects); po=filler.getObject(); po.setObjectID(nextId()); po.setPoolName(name); // Calculate a lifetime and set it po.setMaxAge(calculateMaxAge()); po.activate(); synchronized(pool) { pool.add(po); } getLogger().debug("Added the object to the pool, now have %s", totalObjects()); } else { throw new PoolException("Cannot create another object in the pool"); } return(po); } // Calculate the maximum age of the ``next'' object based on the number // of objects currently in the pool. The more full the pool is, the // less time anything should stay in it. This does nifty burst // compensation. private long calculateMaxAge() { // Default to whatever's in the config long rv=maxAge; synchronized(pool) { int poolSize=totalObjects(); // Only create a new maxAge if we're above our minimum threshold if(poolSize>minObjects) { float percentFull=(float)poolSize/(float)maxObjects; float factor=1-percentFull; rv=(long)((double)rv*factor); // All connections should be available for at least 5 seconds if(rv<MIN_MAX_AGE) { rv=MIN_MAX_AGE; } } } return(rv); } /** * Find out how many objects are in this pool. This will be the sum of * the available and unavailable objects. */ public int totalObjects() { int ret=-1; synchronized(pool) { ret=pool.size(); } return(ret); } private int getPropertyInt(String what, int def) { return(conf.getInt(name + "." + what, def)); } private boolean getPropertyBool(String what, boolean def) { String s=getProperty(what, String.valueOf(def)); Boolean b=Boolean.valueOf(s); return(b.booleanValue()); } private String getProperty(String what, String def) { return(conf.get(name + "." + what, def)); } private static synchronized int nextId() { objectId++; return(objectId); } }