User: mulder
Date: 00/06/02 06:48:47
Added: src/main/org/jboss/minerva/pools ObjectPool.java
ObjectRecord.java PoolEvent.java
PoolEventListener.java PoolGCThread.java
PoolObjectFactory.java PooledObject.java
package.html
Log:
Initial entry of Minerva JDBC Pools into CVS.
Pools, DataSources, and other non-jBoss-dependent code is under
org.jboss.minerva.*
JavaDoc source HTML files are included - the package comments are in
the package.html files in the various packages, and the overview
comments are in org/jboss/minerva/minerva.html
MBeans to load a pool into jBoss are in org.jboss.jdbc
A new logging Writer is on org.jboss.logging.
Revision Changes Path
1.1 jboss/src/main/org/jboss/minerva/pools/ObjectPool.java
Index: ObjectPool.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
import java.io.*;
import java.util.*;
/**
* A generic object pool. You must provide a PoolObjectFactory (or the class
* of a Java Bean) so the pool knows what kind of objects to create. It has
* many configurable parameters, such as the minimum and maximum size of the
* pool, whether to allow the pool to shrink, etc. If the pooled objects
* implement PooledObject, they will automatically be returned to the pool at
* the appropriate times.
* <P>In general, the appropriate way to use a pool is:</P>
* <OL>
* <LI>Create it</LI>
* <LI>Configure it (set factory, name, parameters, etc.)</LI>
* <LI>Initialize it (once done, further configuration is not allowed)</LI>
* <LI>Use it</LI>
* <LI>Shut it down</LI>
* </OL>
* @see org.jboss.minerva.pools.PooledObject
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
public class ObjectPool implements PoolEventListener {
private final static String INITIALIZED = "Pool already initialized!";
private final static PoolGCThread collector = new PoolGCThread();
static {
collector.start();
}
private PoolObjectFactory factory;
private String poolName;
private HashMap objects = null;
private int minSize = 0;
private int maxSize = 0;
private boolean shrinks = false;
private boolean runGC = false;
private float shrinkPercent = 0.33f; // reclaim 1/3 of stale objects
private long shrinkMinIdleMillis = 600000l; // must be unsued for 10 minutes
private long gcMinIdleMillis = 1200000l; // must be idle for 20 minutes
private long gcIntervalMillis = 120000l; // shrink & gc every 2 minutes
private long lastGC = System.currentTimeMillis();
private boolean blocking = false;
private boolean trackLastUsed = false;
private PrintWriter logWriter = null;
/**
* Creates a new pool. It cannot be used until you specify a name and
* object factory or bean class, and initialize it.
* @see #setName
* @see #setObjectFactory
* @see #initialize
*/
public ObjectPool() {}
/**
* Creates a new pool with the specified parameters. It cannot be used
* until you initialize it.
* @param factory The object factory that will create the objects to go in
* the pool.
* @param poolName The name of the pool. This does not have to be unique
* across all pools, but it is strongly recommended (and it may be a
* requirement for certain uses of the pool).
* @see #initialize
*/
public ObjectPool(PoolObjectFactory factory, String poolName) {
setObjectFactory(factory);
setName(poolName);
}
/**
* Creates a new pool with the specified parameters. It cannot be used
* until you initialize it.
* @param javeBeanClass The Class of a Java Bean. New instances for the
* pool will be created with the no-argument constructor, and no
* particular initialization or cleanup will be performed on the
* instances. Use a PoolObjectFactory if you want more control over
* the instances.
* @param poolName The name of the pool. This does not have to be unique
* across all pools, but it is strongly recommended (and it may be a
* requirement for certain uses of the pool).
* @see #initialize
*/
public ObjectPool(Class javaBeanClass, String poolName) {
setObjectFactory(javaBeanClass);
setName(poolName);
}
/**
* Sets the object factory for the pool. The object factory controls the
* instances created for the pool, and can initialize instances given out
* by the pool and cleanup instances returned to the pool.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the object factory after the pool has been
* initialized.
*/
public void setObjectFactory(PoolObjectFactory factory) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
this.factory = factory;
}
/**
* Sets the object factory as a new factory for Java Beans. New instances
* for the pool will be created with the no-argument constructor, and no
* particular initialization or cleanup will be performed on the
* instances.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the object factory after the pool has been
* initialized.
*/
public void setObjectFactory(Class javaBeanClass) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
factory = new BeanFactory(javaBeanClass);
}
/**
* Sets the name of the pool. This is not required to be unique across all
* pools, but is strongly recommended. Certain uses of the pool (such as
* a JNDI object factory) may require it. This must be set exactly once
* for each pool (it may be set in the constructor).
* @throws java.lang.IllegalStateException
* Occurs when you try to set the name of the pool more than once.
*/
public void setName(String name) {
if(poolName != null)
throw new IllegalStateException("Cannot change pool name once set!");
poolName = name;
}
/**
* Gets the name of the pool.
*/
public String getName() {return poolName;}
/**
* Gets a log writer used to record pool events.
*/
public PrintWriter getLogWriter() throws java.sql.SQLException {
return logWriter;
}
/**
* Sets a log writer used to record pool events.
*/
public void setLogWriter(PrintWriter writer) throws java.sql.SQLException {
logWriter = writer;
}
/**
* Sets the minimum size of the pool. The pool always starts with zero
* instances, but once running, it will never shrink below this size. This
* parameter has no effect if shrinking is not enabled. The default is
* zero.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the minimum size after the pool has been
* initialized.
*/
public void setMinSize(int size) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
minSize = size;
}
/**
* Gets the minimum size of the pool.
* @see #setMinSize
*/
public int getMinSize() {return minSize;}
/**
* Sets the maximum size of the pool. Once the pool has grown to hold this
* number of instances, it will not add any more instances. If one of the
* pooled instances is available when a request comes in, it will be
* returned. If none of the pooled instances are available, the pool will
* either block until an instance is available, or return null. The default
* is no maximum size.
* @see #setBlocking
* @param size The maximum size of the pool, or 0 if the pool should grow
* indefinitely (not recommended).
* @throws java.lang.IllegalStateException
* Occurs when you try to set the maximum size after the pool has been
* initialized.
*/
public void setMaxSize(int size) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
maxSize = size;
}
/**
* Gets the maximum size of the pool.
* @see #setMaxSize
*/
public int getMaxSize() {return maxSize;}
/**
* Sets whether the pool should release instances that have not been used
* recently. This is intended to reclaim resources (memory, database
* connections, file handles, etc) during periods of inactivity. This runs
* as often as garbage collection (even if garbage collection is disabled,
* this uses the same timing parameter), but the required period of
* inactivity is different. Also, you may choose to release only a fraction
* of the eligible objects to slow the shrinking further. So the algorithm
* is:
* <UL>
* <LI>Run every <I>n</I> milliseconds.</LI>
* <LI>Count the number of connections that are not in use, and whose last
* used time is greater than the period of inactivity.</LI>
* <LI>Multiply this by the fraction of connections to release, but if the
* last total was greater than one, this will always be at least one.</LI>
* <LI>Attempt to release this many connections, of the ones identified
* above. Do not release any connection that has been marked as in use
* while this runs, and do not allow the pool to shrink below the
* specified minimum size.</LI>
* </UL>
* <P>The default is disabled.</P>
* @see #setGCInterval
* @see #setShrinkMinIdleTime
* @see #setShrinkPercent
* @see #setMinSize
* @throws java.lang.IllegalStateException
* Occurs when you try to set the shrinking state after the pool has been
* initialized.
*/
public void setShrinkingEnabled(boolean allowShrinking) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
shrinks = allowShrinking;
}
/**
* Gets whether shrinking of the pool is enabled.
* @see #setShrinkingEnabled
*/
public boolean isShrinkingEnabled() {return shrinks;}
/**
* Sets whether garbage collection is enabled. This is the process of
* returning objects to the pool if they have been checked out of the pool
* but have not been used in a long periond of time. This is meant to
* reclaim resources, generally caused by unexpected failures on the part
* of the pool client (which forestalled returning an object to the pool).
* This runs on the same schedule as shrinking (if enabled), but objects
* that were just garbage collected will not be eligible for shrinking
* immediately (after all, they presumably represented "active" clients).
* Connections that are garbage collected will be returned immediately if
* a client is blocking waiting for an object. The deafult value is
* disabled.
* @see #setGCMinIdleTime
* @see #setGCInterval
* @throws java.lang.IllegalStateException
* Occurs when you try to set the garbage collection state after the pool
* has been initialized.
*/
public void setGCEnabled(boolean enabled) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
runGC = enabled;
}
/**
* Gets whether garbage collection is enabled.
* @see #setGCEnabled
*/
public boolean isGCEnabled() {return runGC;}
/**
* Sets the shrink percent as a fraction between 0 and 1. This controls
* how many of the available connection which have been idle for too long
* will be released. If set to 1, all eligible connections will be
* released (subject to the minimum size), and if set to 0, only one will
* be released each time (if any are eligible - see the algorithm in
* setShrinkingEnabled). The default value is 33%.
* @see #setShrinkingEnabled
* @throws java.lang.IllegalStateException
* Occurs when you try to set the shrinking percent after the pool
* has been initialized.
* @throws java.lang.IllegalArgumentException
* Occurs when the percent parameter is not between 0 and 1.
*/
public void setShrinkPercent(float percent) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
if(percent < 0f || percent > 1f)
throw new IllegalArgumentException("Percent must be between 0 and 1!");
shrinkPercent = percent;
}
/**
* Gets the shrink percent as a fraction between 0 and 1.
* @see #setShrinkPercent
*/
public float getShrinkPercent() {return shrinkPercent;}
/**
* Sets the minimum idle time to make an object eligible for shrinking. If
* the object is not in use and has not been used for this amount of time,
* it may be released from the pool. If timestamps are enabled, the client
* may update the last used time. Otherwise, the last used time is only
* updated when an object is acquired or released. The default value is
* 10 minutes.
* @see #setShrinkingEnabled
* @param millis The idle time, in milliseconds.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the shrinking idle time after the pool
* has been initialized.
*/
public void setShrinkMinIdleTime(long millis) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
shrinkMinIdleMillis = millis;
}
/**
* Gets the minimum idle time to make an object eligible for shrinking.
* @see #setShrinkMinIdleTime
*/
public long getShrinkMinIdleTime() {return shrinkMinIdleMillis;}
/**
* Sets the minimum idle time to make an object eligible for garbage
* collection. If the object is in use and has not been used for this
* amount of time, it may be returned to the pool. If timestamps are
* enabled, the client may update the last used time (this is generally
* recommended if garbage collection is enabled). Otherwise, the last used
* time is only updated when an object is acquired or released. The default
* value is 20 minutes.
* @see #setGCEnabled
* @param millis The idle time, in milliseconds.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the garbage collection idle time after the
* pool has been initialized.
*/
public void setGCMinIdleTime(long millis) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
gcMinIdleMillis = millis;
}
/**
* Gets the minimum idle time to make an object eligible for garbage
* collection.
* @see #setGCMinIdleTime
*/
public long getGCMinIdleTime() {return gcMinIdleMillis;}
/**
* Sets the length of time between garbage collection and shrinking runs.
* This is inexact - if there are many pools with garbage collection and/or
* shrinking enabled, there will not be a thread for each one, and several
* nearby actions may be combined. Likewise if the collection process is
* lengthy for certain types of pooled objects (not recommended), other
* actions may be delayed. This is to prevend an unnecessary proliferation
* of threads (the total number of which may be limited by your OS, e.g. in
* a "native threads" VM implementation). Note that this parameter controls
* both garbage collection and shrinking - and they will be performed
* together if both are enabled. The deafult value is 2 minutes.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the garbage collection interval after the
* pool has been initialized.
*/
public void setGCInterval(long millis) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
gcIntervalMillis = millis;
}
/**
* Gets the length of time between garbage collection and shrinking runs.
* @see #setGCInterval
*/
public long getGCInterval() {return gcIntervalMillis;}
/**
* Sets whether a request for an object will block if the pool size is
* maxed out and no objects are available. If set to block, the request
* will not return until an object is available. Otherwise, the request
* will return null immediately (and may be retried). If multiple
* requests block, there is no guarantee which will return first. The
* default is not to block (to return null).
* @throws java.lang.IllegalStateException
* Occurs when you try to set the blocking parameter after the
* pool has been initialized.
*/
public void setBlocking(boolean blocking) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
this.blocking = blocking;
}
/**
* Gets whether a request for an object will block if the pool size is
* maxed out and no objects are available.
* @see #setBlocking
*/
public boolean isBlocking() {return blocking;}
/**
* Sets whether object clients can update the last used time. If not, the
* last used time will only be updated when the object is given to a client
* and returned to the pool. This time is important if shrinking or
* garbage collection are enabled (particularly the latter). The default
* is false.
* @throws java.lang.IllegalStateException
* Occurs when you try to set the timestamp parameter after the
* pool has been initialized.
*/
public void setTimestampUsed(boolean timestamp) {
if(objects != null)
throw new IllegalStateException(INITIALIZED);
trackLastUsed = timestamp;
}
/**
* Gets whether object clients can update the last used time.
*/
public boolean isTimestampUsed() {return trackLastUsed;}
/**
* Prepares the pool for use. This must be called exactly once before
* getObject is even called. The pool name and object factory must be set
* before this call will succeed.
* @throws java.lang.IllegalStateException
* Occurs when you try to initialize the pool without setting the object
* factory or name, or you initialize the pool more than once.
*/
public void initialize() {
if(factory == null || poolName == null)
throw new IllegalStateException("Factory and Name must be set before
pool initialization!");
if(objects != null)
throw new IllegalStateException("Cannot initialize more than once!");
objects = new HashMap();
factory.poolStarted(this);
lastGC = System.currentTimeMillis();
collector.addPool(this);
}
/**
* Shuts down the pool. All outstanding objects are closed and all objects
* are released from the pool. No getObject or releaseObject calls will
* succeed after this method is called - and they will probably fail during
* this method call.
*/
public void shutDown() {
collector.removePool(this);
factory.poolClosing(this);
HashMap localObjects = objects;
objects = null;
// close all objects
for(Iterator it = localObjects.values().iterator(); it.hasNext();) {
ObjectRecord rec = (ObjectRecord)it.next();
if(rec.isInUse())
factory.returnObject(rec.getClientObject());
factory.deleteObject(rec.getObject());
rec.close();
}
localObjects.clear();
factory = null;
poolName = null;
}
/**
* Gets an object from the pool. If all the objects in the pool are in use,
* creates a new object, adds it to the pool, and returns it. If all
* objects are in use and the pool is at maximum size, will block or
* return null.
* @see #setBlocking
*/
public Object getObject() {
while(true) {
Iterator it = new HashSet(objects.values()).iterator();
while(it.hasNext()) {
ObjectRecord rec = (ObjectRecord)it.next();
if(!rec.isInUse()) {
try {
rec.setInUse(true);
Object ob = rec.getObject();
Object result = factory.prepareObject(ob);
if(result != ob) rec.setClientObject(result);
if(result instanceof PooledObject)
((PooledObject)result).addPoolEventListener(this);
log("Pool "+this+" gave out pooled object: "+result);
return result;
} catch(ConcurrentModificationException e) {}
}
}
// Serialize creating new connections
synchronized(objects) { // Don't let 2 threads add at the same time
if(minSize == 0 || objects.size() < maxSize) {
Object ob = factory.createObject();
ObjectRecord rec = new ObjectRecord(ob);
objects.put(ob, rec);
Object result = factory.prepareObject(ob);
if(result != ob) rec.setClientObject(result);
if(result instanceof PooledObject)
((PooledObject)result).addPoolEventListener(this);
log("Pool "+this+" gave out new object: "+result);
return result;
} else System.out.println("Object Pool "+poolName+" is full
("+objects.size()+"/"+maxSize+")!");
}
if(blocking) {
log("Pool "+this+" waiting for a free object");
synchronized(this) {
try {
wait();
} catch(InterruptedException e) {}
}
} else {
break;
}
}
log("Pool "+this+" couldn't find an object to return!");
return null;
}
/**
* Sets the last used time for an object in the pool that is currently
* in use. If the timestamp parameter is not set, this call does nothing.
* Otherwise, the object is marked as last used at the current time.
* @see #setTimestampUsed
*/
public void setLastUsed(Object object) {
if(!trackLastUsed) return;
Object ob = factory.translateObject(object);
ObjectRecord rec = (ObjectRecord)objects.get(ob);
rec.setLastUsed();
}
/**
* Returns an object to the pool. This must be the exact object that was
* given out by getObject, and it must be returned to the same pool that
* generated it. If other clients are blocked waiting on an object, the
* object may be re-released immediately.
* @throws java.lang.IllegalArgumentException
* Occurs when the object is not in this pool.
*/
public void releaseObject(Object object) {
synchronized(object) {
Object pooled = null;
try {
pooled = factory.translateObject(object);
} catch(Exception e) {
return; // We can't release it if the factory can't recognize
it
}
if(pooled == null) // We can't release it if the factory can't recognize
it
return;
ObjectRecord rec = (ObjectRecord)objects.get(pooled);
if(rec == null) // Factory understands it, but we don't
throw new IllegalArgumentException("Object "+object+" is not in pool
"+poolName+"!");
if(!rec.isInUse()) return; // Must have been released by GC?
if(object instanceof PooledObject)
((PooledObject)object).removePoolEventListener(this);
factory.returnObject(object);
rec.setInUse(false);
}
log("Pool "+this+" returned object "+object+" to the pool.");
if(blocking) {
synchronized(this) {
notify();
}
}
}
private int getUsedCount() {
if(objects == null) return 0;
int total = 0;
Iterator it = new HashSet(objects.values()).iterator();
while(it.hasNext()) {
ObjectRecord or = (ObjectRecord)it.next();
if(or.isInUse()) ++total;
}
return total;
}
/**
* Returns the pool name and status.
*/
public String toString() {
return poolName+" ["+getUsedCount()+"/"+(objects == null ? 0 :
objects.size())+"/"+(maxSize == 0 ? "Unlimited" : Integer.toString(maxSize))+"]";
}
// ---- PoolEventListener Implementation ----
/**
* If the object has been closed, release it.
*/
public void objectClosed(PoolEvent evt) {
releaseObject(evt.getSource());
}
/**
* If the object had an error, we assume this will propogate and preclude it
* from being closed, so we will close it.
*/
public void objectError(PoolEvent evt) {
releaseObject(evt.getSource());
}
/**
* If we're tracking the last used times, update the last used time for the
* specified object.
*/
public void objectUsed(PoolEvent evt) {
if(!trackLastUsed) return;
setLastUsed(evt.getSource());
}
long getNextGCMillis(long now) {
if(!runGC) return Long.MAX_VALUE;
return lastGC + gcIntervalMillis - now;
}
// Allow GC if we're within 10% of the desired interval
boolean isTimeToGC() {
return System.currentTimeMillis() >=
lastGC + Math.round((float)gcIntervalMillis * 0.9f);
}
void runGCandShrink() {
if(runGC) { // Garbage collection - return any object that's been out too
long with no use
Iterator it = new HashSet(objects.values()).iterator();
while(it.hasNext()) {
ObjectRecord rec = (ObjectRecord)it.next();
if(rec.isInUse() && rec.getMillisSinceLastUse() >= gcMinIdleMillis) {
releaseObject(rec.getClientObject());
}
}
}
if(shrinks) { // Shrinking the pool - remove objects from the pool if they
have not been used in a long time
// Find object eligible for removal
HashSet eligible = new HashSet();
Iterator it = new HashSet(objects.values()).iterator();
while(it.hasNext()) {
ObjectRecord rec = (ObjectRecord)it.next();
if(!rec.isInUse() && rec.getMillisSinceLastUse() >
shrinkMinIdleMillis)
eligible.add(rec);
}
// Calculate number of objects to remove
int count = Math.round(eligible.size() * shrinkPercent);
if(count == 0 && eligible.size() > 0) count = 1;
// Attempt to remove that many objects
it = eligible.iterator();
for(int i=0; i<count; i++) {
if(objects.size() <= minSize) break; // Don't fall below the minimum
if(!it.hasNext()) break; // If the objects have meanwhile been
checked out, we're done
try {
ObjectRecord rec = (ObjectRecord)it.next();
rec.setInUse(true); // Don't let someone use it while we
destroy it
Object pooled = rec.getObject();
objects.remove(pooled);
try {
factory.deleteObject(pooled);
} catch(Exception e) {
log("Pool "+this+" factory ("+factory.getClass().getName()+"
delete error: "+e);
}
rec.close();
} catch(ConcurrentModificationException e) {
--i;
}
}
}
lastGC = System.currentTimeMillis();
}
private void log(String message) {
if(logWriter != null)
logWriter.println(message);
}
}
class BeanFactory extends PoolObjectFactory {
private Class beanClass;
public BeanFactory(Class beanClass) {
try {
beanClass.getConstructor(new Class[0]);
} catch(NoSuchMethodException e) {
throw new IllegalArgumentException("Bean class doesn't have no-arg
constructor!");
}
this.beanClass = beanClass;
}
public Object createObject() {
try {
return beanClass.newInstance();
} catch(Exception e) {
System.out.println("Unable to create instance of
"+beanClass.getName()+": "+e);
}
return null;
}
}
1.1 jboss/src/main/org/jboss/minerva/pools/ObjectRecord.java
Index: ObjectRecord.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
import java.util.*;
/**
* Stores the properties of an object in a pool.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
class ObjectRecord {
private long created;
private long lastUsed;
private Object object;
private Object clientObject;
private boolean inUse;
/**
* Created a new record for the specified pooled object. Objects default to
* being in use when created, so that they can't be stolen away from the
* creator by another thread.
*/
public ObjectRecord(Object ob) {
created = lastUsed = System.currentTimeMillis();
object = ob;
inUse = true;
}
/**
* Gets the date when this connection was originally opened.
*/
public Date getCreationDate() {
return new Date(created);
}
/**
* Gets the date when this connection was last used.
*/
public Date getLastUsedDate() {
return new Date(lastUsed);
}
/**
* Gets the time (in milliseconds) since this connection was last used.
*/
public long getMillisSinceLastUse() {
return System.currentTimeMillis() - lastUsed;
}
/**
* Tells whether this connection is currently in use. This is not
* synchronized since you probably want to synchronize at a higher level
* (if not in use, do something), etc.
*/
public boolean isInUse() {
return inUse;
}
/**
* Sets whether this connection is currently in use.
* @throws java.util.ConcurrentModificationException
* Occurs when the connection is already in use and it is set to be
* in use, or it is not in use and it is set to be not in use.
*/
public synchronized void setInUse(boolean inUse) throws
ConcurrentModificationException {
if(this.inUse == inUse)
throw new ConcurrentModificationException();
this.inUse = inUse;
lastUsed = System.currentTimeMillis();
if(!inUse) clientObject = null;
}
/**
* Sets the last used time to the current time.
*/
public void setLastUsed() {
lastUsed = System.currentTimeMillis();
}
/**
* Gets the pooled object associated with this record.
*/
public Object getObject() {
return object;
}
/**
* Sets the client object associated with this object. Not always used.
*/
public void setClientObject(Object o) {
clientObject = o;
}
/**
* Gets the client object associated with this object. If there is none,
* returns the normal object (which is the default).
*/
public Object getClientObject() {
return clientObject == null ? object : clientObject;
}
/**
* Shuts down this object - it will be useless thereafter.
*/
public void close() {
object = null;
clientObject = null;
created = lastUsed = Long.MAX_VALUE;
inUse = true;
}
}
1.1 jboss/src/main/org/jboss/minerva/pools/PoolEvent.java
Index: PoolEvent.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
import java.util.EventObject;
/**
* An event caused by an object in a pool. The event indicates that the
* object was used, closed, or had an error occur. The typical response is
* to update the last used time in the pool for used events, and return the
* object to the pool for closed or error events.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
public class PoolEvent extends EventObject {
/**
* The object has been closed and should be returned to the pool. Note this
* is not a final sort of closing - the object must still be able to be
* returned to the pool and reused.
*/
public final static int OBJECT_CLOSED = -8986432;
/**
* Indicates that an error occured with the object. The object will be
* returned to the pool, since there will presumably be an exception
* thrown that precludes the client from closing it or returning it
* normally. This should not be used for final or destructive errors - the
* object must stil be able to be returned to the pool and reused.
*/
public final static int OBJECT_ERROR = -8986433;
/**
* Indicates that the object was used, and its timestamp should be updated
* accordingly (if the pool tracks timestamps).
*/
public final static int OBJECT_USED = -8986434;
private int type;
/**
* Create a new event.
* @param source The source must be the object that was returned from the
* getObject method of the pool - the pool will use the source for
* some purpose depending on the type, so it cannot be an arbitrary
* object.
* @param type The event type.
*/
public PoolEvent(Object source, int type) {
super(source);
if(type != OBJECT_CLOSED && type != OBJECT_ERROR && type != OBJECT_USED)
throw new IllegalArgumentException("Invalid event type!");
this.type = type;
}
/**
* Gets the event type.
* @see #OBJECT_CLOSED
* @see #OBJECT_USED
* @see #OBJECT_ERROR
*/
public int getType() {
return type;
}
}
1.1 jboss/src/main/org/jboss/minerva/pools/PoolEventListener.java
Index: PoolEventListener.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
/**
* A listener for object pool events.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
public interface PoolEventListener {
/**
* The pooled object was closed and should be returned to the pool.
*/
public void objectClosed(PoolEvent evt);
/**
* The pooled object had an error and should be returned to the pool.
*/
public void objectError(PoolEvent evt);
/**
* The pooled object was used and its timestamp should be updated.
*/
public void objectUsed(PoolEvent evt);
}
1.1 jboss/src/main/org/jboss/minerva/pools/PoolGCThread.java
Index: PoolGCThread.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
import java.util.*;
/**
* Runs garbage collection on all available pools. Only one GC thread is
* created, no matter how many pools there are - it just tries to calculate
* the next time it should run based on the figues for all the pools. It will
* run on any pools which are "pretty close" to their requested time.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
class PoolGCThread extends Thread {
private HashSet pools = new HashSet();
PoolGCThread() {
super("Minerve ObjectPool GC Thread");
setDaemon(true);
}
public void run() {
while(true) {
// Don't do anything while there's nothing to do
waitForPools();
// Figure out how long to sleep
long delay = getDelay();
// Sleep
if(delay > 0l) {
try {
sleep(delay);
} catch(InterruptedException e) {}
}
// Run garbage collection on eligible pools
runGC();
}
}
private synchronized void waitForPools() {
while(pools.size() == 0) {
try {
wait();
} catch(InterruptedException e) {
}
}
}
private synchronized long getDelay() {
long next = Long.MAX_VALUE;
long now = System.currentTimeMillis();
long current;
for(Iterator it = pools.iterator(); it.hasNext();) {
ObjectPool pool = (ObjectPool)it.next();
current = pool.getNextGCMillis(now);
if(current < next) next = current;
}
return next >= 0l ? next : 0l;
}
private synchronized void runGC() {
for(Iterator it = pools.iterator(); it.hasNext();) {
ObjectPool pool = (ObjectPool)it.next();
if(pool.isTimeToGC())
pool.runGCandShrink();
}
}
synchronized void addPool(ObjectPool pool) {
if(pool.isGCEnabled())
pools.add(pool);
notify();
}
synchronized void removePool(ObjectPool pool) {
pools.remove(pool);
}
}
1.1 jboss/src/main/org/jboss/minerva/pools/PoolObjectFactory.java
Index: PoolObjectFactory.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
/**
* Creates objects to be used in an object pool. This is a class instead of
* an interface so you can ignore any of the methods you don't need.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
public abstract class PoolObjectFactory {
/**
* Creates a new object to be stored in an object pool. This is the
* instance that will actually be sotred in the pool and reused. If you
* want to wrap it somehow, or return instances of a different type that
* refers to these, you can implement prepareObject.
* @see #prepareObject
*/
public abstract Object createObject();
/**
* Indicates to the factory that the pool has started up. This will be
* called before any other methods of the factory are called (on behalf of
* this pool).
* @param pool The pool that is starting. You may decide to allow
* multiple pools you use your factory, or to restrict it to a one-to-one
* relationship.
*/
public void poolStarted(ObjectPool pool) {
}
/**
* Prepares an object to be returned to the client. This may be used to
* configure the object somehow, or actually return a completely different
* object (so long as the original can be recovered in translateObject or
* returnObject). This will be called whenever an object is returned to
* the client, whether a new object or a previously pooled object.
* @param pooledObject The object in the pool, as created by createObject.
* @return The object to return to the client. If different, the pooled
* object must be recoverable by translateObject and returnObject.
*/
public Object prepareObject(Object pooledObject) {
return pooledObject;
}
/**
* If the objects supplied to the client are different than the objects in
* the pool, extracts a pool object from a client object. This should only
* be called between prepareObject and returnObject for any given pool
* object (and associated client object). However, it may be called once
* after an object has been released if the garbage collector and a client
* attempt to release an object at the same time. In this case, this
* method may work, return null, or throw an exception and the pool will
* handle it gracefully. The default implementation returns the parameter
* object (assumes client and pooled objects are the same).
* @param clientObject The client object, as returned by prepareObject
* @return The pooled object, as originally returned by createObject
*/
public Object translateObject(Object clientObject) {
return clientObject;
}
/**
* Prepares an object to be returned to the pool. Any cleanup or reset
* actions should be performed here. This also has the same effect as
* translateObject (only relevant if the pooled objects are different than
* the objects supplied to the client).
* @param clientObject The client object, as returned by prepareObject
* @return The pooled object, as originally returned by createObject, ready
* to be put back in the pool and reused.
*/
public Object returnObject(Object clientObject) {
return clientObject;
}
/**
* Indicates to the factory that the pool is closing down. This will be
* called before all the instances are destroyed. There may be calls to
* returnObject or translateObject after this, but no calls to
* createObject or prepareObject (on behalf of this pool).
* @param pool The pool that is closing. You may decide to allow
* multiple pools you use your factory, or to restrict it to a one-to-one
* relationship.
*/
public void poolClosing(ObjectPool pool) {
}
/**
* Permanently closes an object, after it is removed from the pool. The
* object will not be returned to the pool - after this, it is gone. This
* is called when the pool shrinks, and when the pool is shut down.
*/
public void deleteObject(Object pooledObject) {
}
}
1.1 jboss/src/main/org/jboss/minerva/pools/PooledObject.java
Index: PooledObject.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.minerva.pools;
/**
* Optional interface for an object in an ObjectPool. If the objects created
* by the ObjcetFactory implement this, the pool will register as a listener
* when an object is checked out, and deregister when the object is returned.
* Then if the object sends a close or error event, the pool will return the
* object to the pool without the client having to do so explicitly.
* @version $Revision: 1.1 $
* @author Aaron Mulder ([EMAIL PROTECTED])
*/
public interface PooledObject {
/**
* Adds a new listener.
*/
public void addPoolEventListener(PoolEventListener listener);
/**
* Removes a listener.
*/
public void removePoolEventListener(PoolEventListener listener);
}
1.1 jboss/src/main/org/jboss/minerva/pools/package.html
Index: package.html
===================================================================
<HTML>
<HEAD>
<TITLE>Minerva Pools: Package org.jboss.minerva.pools</TITLE>
</HEAD>
<BODY BGCOLOR="WHITE">
<P>Contains classes required for generic object pools. With these classes,
you can create pools for any type of objects - all you do is supply the
object factory. There are specific object factory implementations for
JDBC connections in the package <B>org.minerva.factories</B>.</P>
<P>This documentation links to the J2SE v1.3 documentation on the JavaSoft
web site, but the classes are compatible with v1.2 as well.</P>
</BODY>
</HTML>