All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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