/* * Copyright (c) 2002-2007 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package net.jxta.impl.util; import net.jxta.logging.Logging; import java.util.logging.Level; import java.util.logging.Logger; /** * This class does not in itself allocate anything; it just does accounting. * Its role is to make sure that resource consumers ("accounts") * are guaranteed to be able to hold a pre-determined number of items, * the extra being granted on a first-come-first-serve basis. * It just replies yes/no to an account that wants to allocate an item. * Synchronization is external. * * <p/><em>Note that this is all essentially a limiter device. It assumes * that the resources that are dispatched in that way are not otherwise * in short supply.</em> * * <p/>The rules of the game are as follows: * <p/>At initialization, an absolute maximum authorized number of items * is computed. All item reservations and authorizations are done * within this budget. * <p/>At any given point in time, out of this maximum, a number of items are * permanently reserved for the minimum guaranteed to each current account, * a number of items are set aside for future accounts guarantee reservation, * and the rest is open for dynamic attribution on a first come first serve * basis. * * <p/>The current strategy is as follows: * * The initialization parameters are:<ul> * <li>minimum number of guaranteed accounts: {@code minAccounts}</li> * <li>minimum commitment to new accounts up to minAccounts: {@code minReserve}</li> * <li>maximum commitment to new accounts: {@code maxReserve}</li> * <li>extra number of dynamically allocatable items: {@code extraItems}</li> * </ul> * * <p/>We infer the number of items dedicated to reservation: {@code reservedItems} * That is {@code minReserve * minAccounts}. * * <p/>Accounts can ask for a commitment in excess of minReserve. Any reservation * made by an account beyond the minimum is satisfied from extraItems * limited by what's available and maxReserve. When minAccounts have * registered, it is possible that reservedItems is exhausted. New accounts * are then accepted on a best effort basis using extra items exclusively. This * may cause such new accounts to be given a commitment inferior to minReserve, * including zero. It is up to the account to reject the offer and give up * by closing, or to go along with the offer. At this time, we do not try * to raise the commitment made to an account while it is registered. * * <p/>During the life of the account, items are allocated first from the set * reserved by this account. If the account is out of reserved items, an attempt * is made at getting the item from extraItems. * * <p/>For each account we count the number of items reserved from reservedItems, * reserved from extraItems, allocated from the local reserved items * and allocated from extraItems separately. When an item is released, it is * accounted to extraItems if the account had anything allocated from * extra items, or to the local reserved items. * When an account goes away, the number of items that were reserved from * reserveItems go back to reserveItems and likewise for those coming * from extraItems. This is done rather than giving priority to reserve * items so that the system does not favor reservation beyond its initial * parameters when an account goes away under load. * * <p/>When resources are scarce, two modes of operations are available. * <dl> * <dt>Unfair</dt> * <dd>each account keeps its items as long it has a use for them. If * the allocation of a new item is denied for an account, the account just has * to live with it and try again the next time more items are desired. * <dd> * <dt>RoundRobin<dt> * <dd>Each account releases each item after one use. When allocation * of a new item is denied for an account by reason of item shortage, the * account is placed on a list of eligible accounts. Every time an item is * released, it is re-assigned to the oldest eligible account. * </dd> * </dl> * <p/>From an API point of view the difference is not visible: account users * are advised to release items after one use. Release returns the account to * which the item has been re-assigned. If RoundRobin is not used, then * the item is always re-assigned to the account that releases it unless it * is not needed, in which case it returns to the available pool. * So, with round-robin off the following is true: * <pre> * a.releaseItem() == (a.needs ? a : null); * </pre> */ public class ResourceDispatcher { /** * Logger */ private final static transient Logger LOG = Logger.getLogger(ResourceDispatcher.class.getName()); private long extraItems; private long reservedItems; private final long maxReservedPerAccount; private final long minReservedPerAccount; private final long maxExtraPerAccount; private final long minExtraPoolSize; private int nbEligibles; private final String myName; class ClientAccount extends Dlink implements ResourceAccount { /** * Tells whether this account has any use for extra resources * or not. This feature is required to support roundRobin mode * properly when resources are scarce. */ private boolean needs; /** * The number of items reserved for this account that may still be * allocated. This decrements when we grant an item allocation. When * it is <= 0, new items may be obtained from extraItems. If obtained * we still decrement. When an item is released, if this counter is * < 0 we return the item to extra items. This counter gets * incremented either way. */ private long nbReserved; /** * The number out of nbReserved that is due back in reservedItems * when this account disappears. * The rest goes back to extraItems. * NOTE: If we go away with items unaccounted for, we take the option * of accounting them as allocated. In other words, that amount is * not returned to its right full item account. That's why we do not * need to keep track of allocated items. The leak is felt * by this allocator. Alternatively we could pretend that the * leaked resources are not ours; but that might break the actual * allocator of the resource if it relies on our accounting. */ private long fromReservedItems; /** * Same idea but they have been reserved by reducing the number * of extra items available. */ private final long fromExtraItems; /** * The limit for extra items allocation. * When nbReserved is at or below that, extra items cannot be * granted. */ private final long extraLimit; /** * The external object for which this account manages items. * This is an opaque cookie to us. Whatever code invokes * releaseItem knows what to do with it and the re-assigned item, but * it needs to be told which of its own object got an item assigned. */ private Object userObject; /** * Creates a client account with this resource manager. * Not for external use. * @param fromReservedItems * @param fromExtraItems * @param extraLimit * @param userObject */ ClientAccount(long fromReservedItems, long fromExtraItems, long extraLimit, Object userObject) { this.nbReserved = fromReservedItems + fromExtraItems; this.fromReservedItems = fromReservedItems; this.fromExtraItems = fromExtraItems; this.extraLimit = -extraLimit; this.userObject = userObject; this.needs = false; } /** * {@inheritDoc} * * <p/>To accelerate return of resources to the global pool, one * may call close() explicitly. Otherwise it is called by * {@code finalize}. * * <p/>Calling close() or letting the account be GC'ed while some of the * resources have not been returned is an error, may create a leak and * may display a warning message. */ public void close() { notEligible(); userObject = null; if ((nbReserved == 0) && (fromReservedItems == 0) && (fromExtraItems == 0)) { return; } if (nbReserved < (fromReservedItems + fromExtraItems)) { // !!! someone just gave up on its resource controller // without returning the resources ! if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("An account was abandoned with resources still allocated."); } // Release whatever we can. if (nbReserved >= fromReservedItems) { releaseExtra(nbReserved - fromReservedItems); releaseReserved(fromReservedItems); } else if (nbReserved > 0) { releaseReserved(nbReserved); } } else { releaseReserved(fromReservedItems); releaseExtra(fromExtraItems); } nbReserved = 0; } /** * {@inheritDoc} * * <p/>Will close the account. (close is idempotent). */ @Override protected void finalize() throws Throwable { close(); super.finalize(); } /** * {@inheritDoc} */ public boolean isIdle() { return (nbReserved == fromExtraItems + fromReservedItems); } public boolean isEligible() { return isLinked(); } /** * Put that account in the queue of accounts eligible to * receive a resource when one becomes available. */ public void beEligible() { if ((eligibles != null) && !isEligible()) { newEligible(this); } } /** * Remove that account from the queue of accounts eligible to * receive a resource when one becomes available. */ public void notEligible() { if ((eligibles != null) && isEligible()) { unEligible(this); } } // An extra item is being granted to this account (by being reassigned // from another account upon release). private void granted() { // In theory, there cannot be an account that should NOT be granted // an item in the eligibles list. For now, check whether the theory // survives observations. // It could happen that the need flag was turned off while this // account was in the eligible list. That's not realy a problem. // Either it will be released immediately, or we could filter // it in mostEligible(). if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { if (nbReserved <= extraLimit) { LOG.warning("An account that should not get an item was found in the eligibles list"); } } --nbReserved; // We've been assigned an item. No-longer eligible. notEligible(); } /** * {@inheritDoc} */ public boolean obtainQuantity(long quantity) { if ((nbReserved - quantity) < extraLimit) { // That's asking too much. Denied. return false; } if (quantity > nbReserved) { // We need to get some or all of it from the extra items. long toAsk = nbReserved > 0 ? quantity - nbReserved : quantity; long res = holdExtra(toAsk); if (res != toAsk) { // Could not get enough. We got nothing. releaseExtra(res); return false; } } // Now record it. nbReserved -= quantity; if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { if (nbReserved > fromReservedItems + fromExtraItems) { LOG.severe("Incorrect values after obtaining " + quantity + " : [" + this + "]"); } } return true; } /** * {@inheritDoc} */ public boolean obtainItem() { // Set it for consistency. It will get cleared when // the item is used to satisfy the need. needs = true; if (nbReserved > 0) { notEligible(); --nbReserved; return true; // Its pre-reserved. } // This account may deliberately limit the number of extra // items it uses. this translates into a lower limit for // nbReserved when <= 0. if (nbReserved <= extraLimit) { notEligible(); return false; } if (holdExtra(1) == 1) { // Need authorization. notEligible(); --nbReserved; return true; } // We are out of luck but eligible. beEligible(); return false; } /** * {@inheritDoc} */ public void releaseQuantity(long quantity) { if (nbReserved < 0) { releaseExtra(quantity < -nbReserved ? quantity : -nbReserved); } nbReserved += quantity; if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { if (nbReserved > fromReservedItems + fromExtraItems) { LOG.severe("Incorrect values after releasing " + quantity + " : [" + this + "]"); } } } /** * {@inheritDoc} */ public ResourceAccount releaseItem() { if (nbReserved < 0) { if (eligibles == null) { // RoundRobin is OFF either we reuse it or we let // it go. if (needs) { return this; } ++nbReserved; releaseExtra(1); return null; } // RoundRobin is ON, we compete with others for this item. ++nbReserved; // Update our eligibility which depends on extraLimit and // whether we have a use for the item or not. if ((nbReserved > extraLimit) && needs) { beEligible(); } ClientAccount next = mostEligible(); if (next == null) { releaseExtra(1); // noone wants it. return to main pool } else { next.granted(); } return next; } // Since we are (back) in our reserved range, we can't be eligible // for extra. notEligible(); // In reserved range; we keep using the item if we need it. if (needs) { return this; } ++nbReserved; return null; } /** * {@inheritDoc} */ public void inNeed(boolean needs) { this.needs = needs; if ((nbReserved < 0) && (nbReserved > extraLimit) && needs) { beEligible(); } else { notEligible(); } } /** * {@inheritDoc} */ public Object getUserObject() { return userObject; } /** * {@inheritDoc} */ public void setUserObject(Object object) { userObject = object; } /** * {@inheritDoc} */ public long getNbReserved() { return nbReserved; } /** * {@inheritDoc} * * <p/>Returns some human-readable status and identity information useful for debugging. */ @Override public String toString() { return super.toString() + " : needs=" + needs + " nbReserved=" + nbReserved + " fromReservedItems=" + fromReservedItems + " fromExtraItems=" + fromExtraItems + " extraLimit=" + extraLimit; } } /** * The list of eligible accounts. */ private Dlist eligibles; /** * Construct a Fair Resource Allocator with the given parameters: * @param minAccounts The minimum number of client accounts that we want to * guarantee we can handle. <0 means 0 * * @param minReservedPerAccount The minimum reservation request that we will * always grant to accounts as long as we have less than minAccounts <0 means * 0. * @param maxReservedPerAccount The maximum reservation request that we ever * will grant to any given account. <minReservedPerAccount means == * @param extraItems The total number of items that we will authorize * beyond what has been reserved. <0 means 0. * @param maxExtraPerAccount The maximum number of extra items we will ever * let any given account occupy. <0 or >extraItems means ==extraItems. * @param minExtraPoolSize The number of extra items that can never be * taken out of the extra pool to satisfy a reservation request. * @param roundRobin If true, when there is no items available, all * eligible accounts are put in a FIFO. Accounts release items often, and the * oldest account in the FIFO will get it. If false, accounts always keep * items for as long as they can use them, and there is no FIFO of eligible * accounts. Accounts can obtain new resources only if available at the time * they try to aquire it. RoundRobin is more fair but has more overhead. * Neither mode will cause starvation as long as accounts reserve at least * one item each. RoundRobin is most useful when allocating threads. */ public ResourceDispatcher(long minAccounts, long minReservedPerAccount, long maxReservedPerAccount, long extraItems, long maxExtraPerAccount, long minExtraPoolSize, boolean roundRobin, String dispatcherName) { if (minAccounts < 0) { minAccounts = 0; } if (minReservedPerAccount < 0) { minReservedPerAccount = 0; } if (maxReservedPerAccount < minReservedPerAccount) { maxReservedPerAccount = minReservedPerAccount; } if (extraItems < 0) { extraItems = 0; } if (minExtraPoolSize < 0) { minExtraPoolSize = 0; } if ((maxExtraPerAccount < 0) || (maxExtraPerAccount > extraItems)) { maxExtraPerAccount = extraItems; } this.extraItems = extraItems; this.minExtraPoolSize = minExtraPoolSize; this.maxReservedPerAccount = maxReservedPerAccount; this.minReservedPerAccount = minReservedPerAccount; this.reservedItems = minAccounts * minReservedPerAccount; this.maxExtraPerAccount = maxExtraPerAccount; nbEligibles = 0; if (roundRobin) { eligibles = new Dlist(); } this.myName = dispatcherName; } private long holdReserved(long req) { if (req > reservedItems) { req = reservedItems; } reservedItems -= req; return req; } private void releaseReserved(long nb) { reservedItems += nb; } private long holdExtra(long req) { if (req > extraItems) { req = extraItems; } extraItems -= req; return req; } // Get items from the extra pool but only if there is at least // minExtraPoolSize item // left after that. The goal is to make sure we keep at least one // un-reserved item when granting reserved items from the extra pool. // Thanks to that, even accounts that could not get a single reserved // item still stand a chance to make progress by taking turns using // the one extra item left. private long holdExtraKeepSome(long req) { if (extraItems <= minExtraPoolSize) { return 0; } long allowed = extraItems - minExtraPoolSize; if (req > allowed) { req = allowed; } extraItems -= req; return req; } private void releaseExtra(long nb) { extraItems += nb; } private void newEligible(ClientAccount account) { ++nbEligibles; eligibles.putLast(account); } private ClientAccount mostEligible() { if (nbEligibles == 0) { return null; } return (ClientAccount) eligibles.getFirst(); } private void unEligible(ClientAccount account) { --nbEligibles; account.unlink(); } // Not synch; it's just a snapshot for trace purposes. public int getNbEligibles() { return nbEligibles; } /** * Creates and returns a new client account. * * @param nbReq the number of reserved items requested (may not be * always granted in full). A negative value is taken to mean 0. * @param maxExtra the number of additional items that this account * authorizes to be allocated in addition to the reserved ones. This * is typically useful if the items are threads and if some accounts * are not re-entrant. Then nbReq would be 1 and maxExtra would be 0. * It is also permitted to have some accounts receive no items at all * ever by setting nbReq and maxExtra both to zero. A negative maxExtra * is taken as meaning no specified limit, in which case an actual limit * may be set silently. * @param userObject An opaque cookie that the account object will return * when requested. This is useful to relate an account returned by * ClientAccount.releaseItem() to an invoking code relevant object. * @return ResourceAccount An account with this allocator. */ public ResourceAccount newAccount(long nbReq, long maxExtra, Object userObject) { long extra = 0; // reserved from extra pool long reserved = 0; // reserved from reserved pool if (nbReq > maxReservedPerAccount) { nbReq = maxReservedPerAccount; } // Anything beyond the minimum comes from extra items if there's // enough. if (nbReq > minReservedPerAccount) { extra = holdExtraKeepSome(nbReq - minReservedPerAccount); nbReq = minReservedPerAccount; } // Then the minimum comes from reserved items, if we can. reserved = holdReserved(nbReq); nbReq -= reserved; // If there's some letf to be had, it means that we're getting // short on reserved items, we'll try to compensate by getting // more items from extra, but the app should start getting rid // of stale accounts if it can. if (nbReq > 0) { if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) { LOG.info("Accepting extra account on a best effort basis."); } extra += holdExtraKeepSome(nbReq); if (extra + reserved < minReservedPerAccount) { // Even that was not enough to reach our minimal commitment. // The app should realy consider some cleanup. if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("[" + myName + "] Accepting extra account with below-minimal commitment:[" + userObject + "]"); } } } if ((maxExtra > maxExtraPerAccount) || (maxExtra < 0)) { maxExtra = maxExtraPerAccount; } return new ClientAccount(reserved, extra, maxExtra, userObject); } }