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

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