com.sun.grizzly.pool.DynamicPool Maven / Gradle / Ivy
/*
Example of use:
RubyAdapter rh = new RubyAdapter (railsRoot,jrubyLib);
DynamicPool pool = new DynamicPool(rh, 1, true);
pool.start();
would, assuming a correctly-implemented RubyAdapter class, create a pool serving Ruby instances
*/
package com.sun.grizzly.pool;
import com.sun.grizzly.http.SelectorThread;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
//import java.util.List;
/**
* A generic self-sizing pool of objects.
*
* TODO: Backwards compatability? How?
* // Can RubyObjectPool just redirect to DynamicPool with a RubyAdapter?
*
*
* @author TAKAI Naoto
* @author Pramod Gopinath
* @author Vivek Pandey
* @author Jacob Kessler
*/
public class DynamicPool {
/**
* How long to wait before giving up.
*/
public static final long DEFAULT_TIMEOUT = 360L;
/**
* The number of objects.
*/
//private final int defaultNumberOfObjects = 5;
/**
* Object queue
*/
private final BlockingQueue queue = new LinkedBlockingQueue();
/**
* Is Grizzly ARP enabled.
*/
private final boolean asyncEnabled;
private final int numberOfObjects;
// CachedThreadPool size limited by currentlyGeneratingObjects: We want to avoid overloading the system
// during times of high load by trying to generate lots of new objects. However, we may want to
// generate multiple objects at the same time on a large machine
private ExecutorService objectGenerator = Executors.newCachedThreadPool();
private final int maxGeneratingObjects;
// private final int maxGeneratingObjects_def = 1;
/**
* The maximum objects that we want sitting in the queue (idle) at a time)
*/
private AtomicInteger maximumIdleObjects = new AtomicInteger(1);
/*
* currentlyActiveObjects keeps track of how many objects the pool "knows" about, either lent out
* or sitting in the queue. It uses that knowledge, along with the limits on the number of objects,
* to prevent itself from making excessive objects, even under heavy load, since at some point making
* a new objects won't actualy help the load conditions
*
* currentlyGeneratingObjects keeps track of how many objects are scheduled for creation but have not
* yet been added to the queue. In case object creation takes a long time relative to the request
* service time, requests on a capacity pool have a tendency to impatiently request 3 or 4 objects
* while waiting for a new object to initialize, which would otherwise cause 3 or 4 too many objects
* to be created, costing both memory while the object lives (bad) and CPU time to create and remove
* the object (really bad, since object creation should be while under load). currentlyGeneratingObjects
* allows the object creator to refuse both object creation requests and object creation ticks while
* it already has object queued to be created.
*
*/
private volatile int currentlyActiveObjects = 0;
private volatile int currentlyGeneratingObjects = 0;
// Soft limit on the number of objects we will create
// Will adjust based on load
//private AtomicInteger maximumActiveObjects = new AtomicInteger (1);
// Hard minimum and Maximum values for number of objects. The dynamic resizer will not exceed these limits
private final int hardMaxActiveObjects;
private final int hardMinActiveObjects;
/*
* The "Tick" variables are the variables that keep track of how close to the various thresholds
* the pool is.
*/
private AtomicInteger downTicks = new AtomicInteger(0); // Manages increasing the number of runtimes
//private AtomicInteger upTicks = new AtomicInteger(0); // Manages decreasing the number of runtimes
private AtomicInteger queueTicks = new AtomicInteger(0); // Manages the queue length
private AtomicInteger newTicks = new AtomicInteger(0); // Manages new Runtime creation
/*
* Threasholds control how many ticks are required to change their associated property. Decreasing
* these values will make the pool change size more rapidly in response to changing traffic, but
* also makes it more likely to create or delete objects in response to brief spikes or dips in
* incoming requests. A constructor has been provided to allow users to set their own values.
*/
// private final static int threashold_def = 10;
//private final int upThreashold;
private final int downThreashold;
private final int queueThreashold;
private final int newThreashold;
private final long baseTime;
private AtomicLong lastRequest = new AtomicLong();
private final boolean dynamic;
private final boolean validate;
// Tuning Variables
private final int requestTimeout = 50; // Number of ms to initially wait for a runtime
private final boolean fineLog = SelectorThread.logger().isLoggable(Level.FINE); // Should we bother to log things at the "fine" level?
private PoolAdapter objectLib;
/**
* Build a dynamic pool of objects based on a provided PoolAdapter and DynamicPoolConfig.
* DynamicPoolConfig allows all internal options to be set.
* Values from DynamicPoolConfig are copied out, and a reference to the PoolConfig is not stored, so it is
* safe to change values in a DynamicPoolConfig after the pool has been created.
* @param type An object that knows how to create, validate, and dispose of the objects this pool is responsible for
* @param config The config object that contains the internal variables for this pool
*/
public DynamicPool(PoolAdapter type, DynamicPoolConfig config) {
objectLib = type;
numberOfObjects = config.getNumberOfObjects();
maxGeneratingObjects = config.getMaxGeneratingObjects();
hardMaxActiveObjects = config.getHardMaxActiveObjects();
hardMinActiveObjects = config.getHardMinActiveObjects();
// Set max active to average of starting and maximum
//maximumActiveObjects.set((numberOfObjects + hardMaxActiveObjects)/2);
// Set max idle to starting runtimes
maximumIdleObjects.set(numberOfObjects);
// upThreashold = config.getUpThreashold();
downThreashold = config.getDownThreashold();
queueThreashold = config.getQueueThreashold();
newThreashold = config.getNewThreashold();
dynamic = config.isDynamic();
asyncEnabled = config.isAsyncEnabled();
validate = config.shouldValidate();
baseTime = System.currentTimeMillis();
logDynamicStatus();
}
public long getBaseTime() {
return baseTime;
}
public boolean getValidation() {
return validate;
}
/**
* Retrives an object from the object pool.
*
* @return a object.
*/
public T borrowObject() {
lastRequest.set(System.currentTimeMillis());
long time = System.currentTimeMillis();
if (isAsyncEnabled()) {
// check to see if we can get one right now
try {
T gotten = queue.poll(requestTimeout, TimeUnit.MILLISECONDS); // Wait, but only briefly
if (gotten != null) {
if (dynamic) { // Only keep track of statistics if dynamic is enabled
if (queue.size() == 0) { // If we took the last runtime
int localQueue = queueTicks.incrementAndGet();
if (localQueue > queueThreashold) { // Deal with increasing the maximum queue size.
queueTicks.set(0);
int localIdle = maximumIdleObjects.incrementAndGet();
if (localIdle > currentlyActiveObjects) {
maximumIdleObjects.set(currentlyActiveObjects); // Volatile is enough for non-assignment operations (right?)
}
}
} else { // Otherwise, there were at least two idle runtimes, so we have plenty lying around
int localNew = newTicks.decrementAndGet();
if (localNew < 0) {
newTicks.set(0); // may drop an update, oh well
}
int localQueue = queueTicks.decrementAndGet(); // start thinking about reducing the queue size
if (localQueue < -queueThreashold) {
queueTicks.set(0);
// Reduce size of queue;
int localIdle = maximumIdleObjects.decrementAndGet();
if (localIdle < 1) { // Make sure we allow at least one idle runtime
maximumIdleObjects.set(1);
}
}
}
}
long waitTime = System.currentTimeMillis() - time;
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
"Recieved new object from the DynamicPool in " + waitTime + "ms. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active Objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active)");
}
return gotten;
} else { // This is the branch for "We waited 50ms for a runtime, but did not recieve one."
// request that the number of runtimes be increased
if (dynamic) { // think about increasing the maximum number of active runtimes
//int localUp = upTicks.incrementAndGet();
int localQueue = queueTicks.incrementAndGet();
/*
if (localUp > upThreashold) {
upTicks.set(0);
queueTicks.set(0);
maximumIdleObjects.incrementAndGet();
int localMax = maximumActiveObjects.incrementAndGet();
if (localMax > hardMaxActiveObjects) {
maximumActiveObjects.set(hardMaxActiveObjects);
}
} */
if (localQueue > queueThreashold) {
queueTicks.set(0);
int localIdle = maximumIdleObjects.incrementAndGet();
if (localIdle > currentlyActiveObjects) {
maximumIdleObjects.set(currentlyActiveObjects);
}
}
}
voteNewObject(); // Vote for more Objects to be created
// Block until an object becomes available
gotten = queue.poll(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
//System.out.println("Wait time (pool miss): " + (System.currentTimeMillis() - time));
long waitTime = System.currentTimeMillis() - time;
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
"Recieved new object from the DynamicPool in " + waitTime + "ms, but voted for an additional object. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active)");
}
return gotten;
}
} catch (InterruptedException e) {
// Following old InterruptedException behavior
throw new RuntimeException(e);
}
} else {
// old non-async behavior
return queue.poll();
}
}
/**
* Returns object to the object pool.
*
* @param object - object to be returned after use
*/
public void returnObject(T object) {
// check to see if we should bother returning this object to the queue
if (queue.size() < maximumIdleObjects.intValue()) { // Queue is less than max idle, return without complaint
validateAndReturn(object);
if (dynamic) {
int localDown = downTicks.decrementAndGet();
if (localDown < 0) {
downTicks.set(0); // may drop an update, oh well.
}
if (fineLog) {
SelectorThread.logger().log(Level.INFO,
"Returned object to the DynamicPool. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active");
}
}
} else {
if (dynamic) {
int localDown = downTicks.incrementAndGet();
//int localUp = upTicks.decrementAndGet();
int localQueue = queueTicks.decrementAndGet();
if (localDown > downThreashold) {
downTicks.set(0);
if (currentlyActiveObjects > hardMinActiveObjects) {
currentlyActiveObjects--;
// Otherwise, we just allow it to fall on the floor and be cleaned up by the GC
objectLib.dispose(object);
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
"Excessive idle objects: returned object not re-added to the DynamicPool. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active");
}
//System.out.println("Dropped an object\t" + (System.currentTimeMillis() - baseTime) + " " + queue.size());
} else {
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
"Returned object to the DynamicPool due to hard minimum. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active");
}
validateAndReturn(object);
}
} else {
//System.out.println("Downticks at " + (System.currentTimeMillis() - baseTime) + " " + downTicks);
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
"Returned object to the DynamicPool, considering active object reduction. " + currentlyActiveObjects + "/" + hardMaxActiveObjects +
" active objects (" + queue.size() + " idle, " + (currentlyActiveObjects - queue.size()) + " active");
}
validateAndReturn(object);
}
if (localQueue < -queueThreashold && maximumIdleObjects.intValue() > 0) {
// Reduce the number of runtimes we are willing to hold around
queueTicks.set(0);
int localIdle = maximumIdleObjects.decrementAndGet();
//int localActive = maximumActiveObjects.decrementAndGet();
if (localIdle < 1) {
maximumIdleObjects.set(1);
}
//if (localActive < hardMinActiveObjects) {
// maximumActiveObjects.set(hardMinActiveObjects);
//}
}
} else {
if (currentlyActiveObjects > hardMinActiveObjects) {
// Need this as a sanity check
currentlyActiveObjects--;
objectLib.dispose(object);
}
}
}
}
private void validateAndReturn(T object) {
if (validate) {
if (objectLib.validate(object)) {
queue.offer(object);
} else {
// invalid object returned!
currentlyActiveObjects--; // CAO is a volatile, so we don't need to sync around it here, which is good since dispose() may take a while
objectLib.dispose(object);
// need to replace it
makeNewObject();
}
} else {
queue.offer(object);
}
}
/**
* Starts the object pool. Calling this multiple times on the same object pool will cause undefined, and probably bad, behavior
*/
public void start() {
try {
int pnum = Runtime.getRuntime().availableProcessors();
//TODO: re-enable multiple runtime creation for jruby >= 1.1.5
//ExecutorService exec = Executors.newFixedThreadPool(Math.min(pnum,numberOfObjects));
ExecutorService exec = Executors.newFixedThreadPool(1); // Hides the lock-on-start Rails issue, for now
SelectorThread.logger().log(Level.FINE,
"Dynamic pool creation threadpool starting with " + Math.min(pnum,numberOfObjects) + " threads");
for (int i = 0; i < numberOfObjects; i++) {
currentlyActiveObjects++;
exec.execute(new Runnable() {
public void run() {
long startTime = System.currentTimeMillis();
T newObject = objectLib.initializeObject();
SelectorThread.logger().log(Level.INFO,
"New instance created in " + (System.currentTimeMillis() - startTime) + " milliseconds");
queue.offer(newObject);
}
});
}
SelectorThread.logger().log(Level.FINE,
"Dynamic pool creation threadpool task submission finished, sending shutdown command");
exec.shutdown();
if (exec.awaitTermination(numberOfObjects * 30, TimeUnit.SECONDS)) {
// Wait for 30 seconds per runtime for it to initialize
SelectorThread.logger().log(Level.FINE,
"Dynamic pool initialization finished!");
} else {
exec.shutdownNow();
currentlyActiveObjects = queue.size();
SelectorThread.logger().log(Level.WARNING,
"Dynamic pool did not finish initialization in a reasonable amount of time! Only " + currentlyActiveObjects +
" of " + numberOfObjects + " initialized sucessfully!");
}
/* Commented out until we decide that the watcher should be implemented
// Start watcher thread. This solves the problem of not decreasing the pool while there is no load.
// This fix will decrease it very slowly (one runtime every two minutes) of no load.
lastRequest.set(System.currentTimeMillis());
objectGenerator.execute (new Runnable() {
public void run() {
while (!objectGenerator.isShutdown()) {
try {
// While the pool is still running
Thread.sleep(1200);
long time = System.currentTimeMillis();
if (lastRequest.get() - time > 1000) {
// It's been more than a second since the last request
if (queue.size() > hardMinActiveObjects) {
// If there is the potential to drop a runtime, simulate a request
T temp = borrowObject();
returnObject(temp);
}
}
} catch (InterruptedException e) {
SelectorThread.logger().log(Level.INFO, "Pool watcher thread interrupted. This should only happen on shutdown");
}
}
}
});
*/
} catch (InterruptedException e) {
// Interrupting us is a bad thing. We don't know how many have completed, and don't have a way to ask the executor
// Make a guess, warn the user, and pass the interruption up.
currentlyActiveObjects = queue.size();
SelectorThread.logger().log(Level.WARNING,
"Dynamic pool startup interrupted! Active runtime count cannot be guaranteed.");
Thread.currentThread().interrupt();
}
}
/**
* Vote for the creation of a new object, to be called when we run out of objects in the queue
*/
private void voteNewObject() {
if ((currentlyActiveObjects < hardMaxActiveObjects) && (currentlyGeneratingObjects < maxGeneratingObjects)) {
int localNew = newTicks.addAndGet(2);
//System.out.println("New Ticks: " + newTicks);
if (localNew > newThreashold) { // Potential for extra object creation here, but extremely unlikely. Extra creation is handled correctly.
newTicks.set(0);
makeNewObject();
}
}
if (currentlyActiveObjects < hardMinActiveObjects) {
makeNewObject();
}
}
/**
* Creates a new object, to be called when we are sure that we want a new object
*/
private void makeNewObject() {
//System.out.println("Currently Active: " + currentlyActiveObjects + " of " + maximumActiveObjects + ", Making " + currentlyGeneratingObjects + " of " + maxGeneratingObjects);
if ((currentlyActiveObjects < hardMaxActiveObjects) && (currentlyGeneratingObjects < maxGeneratingObjects)) {
currentlyActiveObjects++;
currentlyGeneratingObjects++;
objectGenerator.submit(new Runnable() {
public void run() {
try {
long startTime = System.currentTimeMillis();
T newObject = objectLib.initializeObject();
SelectorThread.logger().log(Level.INFO,
"New instance (#" + currentlyActiveObjects + ") created in " + (System.currentTimeMillis() - startTime) + " milliseconds");
queue.offer(newObject);
} catch (Exception e) {
currentlyActiveObjects--; // If object creation fails, we didn't get an object.
} finally {
currentlyGeneratingObjects--; // In all cases, decrement our generation count when we finish
}
}
});
}
}
/**
* Shutdowns the object pool.
*/
public void stop() {
for (T thing : queue) {
objectLib.dispose(thing);
}
queue.clear();
// Stop our object-creation thread
try {
objectGenerator.shutdown();
objectGenerator.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
SelectorThread.logger().log(Level.WARNING,
"Object Pool interrupted on shutdown!");
}
}
public int getNumberOfObjects() {
return numberOfObjects;
}
public BlockingQueue getObjectQueue() {
return queue;
}
public boolean isAsyncEnabled() {
return asyncEnabled;
}
private void logDynamicStatus() {
// logs the min, max, etc. values of the dynamic pool on startup
if (dynamic) {
SelectorThread.logger().log(Level.INFO,
"Dynamic pool created. Initial runtimes will be " + numberOfObjects + ", hard minimum is " + hardMinActiveObjects +
", hard maximum is " + hardMaxActiveObjects +
". If you experiance out of memory errors, consider increasing the heap size or " +
"setting the jruby.runtime.min or jruby.runtime.max Java system properties. If " +
"starting GlassFish using java CLI then provide it as system property, such as -Djruby.runtime.min=1 -Djruby.runtime.max=2" +
", otherwise make an entry into $GLASSFISH_INSTALL/domains/domain1/config/domain.xml, such as " +
"-Djruby.runtime.min=1 -Djruby.runtime.max=2 .");
} else {
SelectorThread.logger().log(Level.INFO,
"Pool started without dynamic resizing enabled. Pool will not attempt to determine the upper and lower bounds that it should be using, and will stay at " + numberOfObjects);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy