com.sun.grizzly.pool.DynamicPool Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.grizzly.pool;
import com.sun.grizzly.http.SelectorThread;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.*;
import java.util.logging.Level;
/**
* A generic self-sizing pool of objects.
* 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
*
* @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,
Messages.format(Messages.DYNAMICPOOL_RECEIVED_NEW_OBJECT,
waitTime, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(), (currentlyActiveObjects - queue.size())));
}
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,
Messages.format(Messages.DYNAMICPOOL_RECEIVED_NEW_OBJECT_VOTED,
waitTime, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(),
(currentlyActiveObjects - queue.size())));
}
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,
Messages.format(Messages.DYNAMICPOOL_RETURNED_OBJECT, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(), currentlyActiveObjects - queue.size()));
}
}
} 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,
Messages.format(Messages.DYNAMICPOOL_EXCESSIVE_OBJECTS, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(), currentlyActiveObjects - queue.size()));
}
//System.out.println("Dropped an object\t" + (System.currentTimeMillis() - baseTime) + " " + queue.size());
} else {
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
Messages.format(Messages.DYNAMICPOOL_RETURNED_HARD_MINIMUM, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(), currentlyActiveObjects - queue.size()));
}
validateAndReturn(object);
}
} else {
//System.out.println("Downticks at " + (System.currentTimeMillis() - baseTime) + " " + downTicks);
if (fineLog) {
SelectorThread.logger().log(Level.FINE,
Messages.format(Messages.DYNAMICPOOL_RETURNED_OBJECT_REDUCTION, currentlyActiveObjects,
hardMaxActiveObjects, queue.size(), currentlyActiveObjects - queue.size()));
}
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
* @param threads the number of threads to start for object generation. Each thread will attempt to initialize one object
*/
public void start(int threads) {
try {
ExecutorService exec = Executors.newFixedThreadPool(threads);
SelectorThread.logger().log(Level.FINE, Messages.format(Messages.DYNAMICPOOL_STARTING_THREADPOOL, 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,
Messages.format(Messages.DYNAMICPOOL_NEWINSTANCE_CREATION_TIME,
System.currentTimeMillis() - startTime));
queue.offer(newObject);
}
});
}
SelectorThread.logger().log(Level.FINE, Messages.format(Messages.DYNAMICPOOL_SHUTDOWN));
exec.shutdown();
if (exec.awaitTermination(numberOfObjects * 30, TimeUnit.SECONDS)) {
// Wait for 30 seconds per runtime for it to initialize
SelectorThread.logger().log(Level.FINE, Messages.format(Messages.DYNAMICPOOL_INIT_FINISHED));
} else {
exec.shutdownNow();
currentlyActiveObjects = queue.size();
SelectorThread.logger().log(Level.WARNING,
Messages.format(Messages.DYNAMICPOOL_INIT_ERR, currentlyActiveObjects, numberOfObjects));
}
/* 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, Messages.format(Messages.DYNAMICPOOL_INIT_INTERRUPTED));
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,
Messages.format(Messages.DYNAMICPOOL_NEW_INSTANCE, currentlyActiveObjects, System.currentTimeMillis() - startTime));
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, Messages.format(Messages.DYNAMICPOOL_SHUTDOWN_INTERRUPTED));
}
}
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,
Messages.format(Messages.DYNAMICPOOL_STATUS, numberOfObjects, hardMinActiveObjects, hardMaxActiveObjects));
} else {
SelectorThread.logger().log(Level.INFO,
Messages.format(Messages.DYNAMICPOOL_DISABLED, numberOfObjects));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy