2 * Copyright (c) 2002-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * 3. The end-user documentation included with the redistribution, if any, must
17 * include the following acknowledgment: "This product includes software
18 * developed by Sun Microsystems, Inc. for JXTA(TM) technology."
19 * Alternately, this acknowledgment may appear in the software itself, if
20 * and wherever such third-party acknowledgments normally appear.
22 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
23 * not be used to endorse or promote products derived from this software
24 * without prior written permission. For written permission, please contact
25 * Project JXTA at http://www.jxta.org.
27 * 5. Products derived from this software may not be called "JXTA", nor may
28 * "JXTA" appear in their name, without prior written permission of Sun.
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
33 * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
44 * Please see the license information page at :
45 * <http://www.jxta.org/project/www/license.html> for instructions on use of
46 * the license in source files.
48 * ====================================================================
50 * This software consists of voluntary contributions made by many individuals
51 * on behalf of Project JXTA. For more information on Project JXTA, please see
52 * http://www.jxta.org.
54 * This license is based on the BSD license adopted by the Apache Foundation.
57 package net.jxta.impl.util;
60 import java.util.LinkedList;
62 import java.util.logging.Logger;
63 import java.util.logging.Level;
64 import net.jxta.logging.Logging;
66 import net.jxta.impl.util.ResourceAccount;
70 * This class does not in itself allocate anything; it just does accounting.
71 * Its role is to make sure that resource consumers ("accounts")
72 * are guaranteed to be able to hold a pre-determined number of items,
73 * the extra being granted on a first-come-first-serve basis.
74 * It just replies yes/no to an account that wants to allocate an item.
75 * Synchronization is external.
77 * <p/><em>Note that this is all essentially a limiter device. It assumes
78 * that the resources that are dispatched in that way are not otherwise
79 * in short supply.</em>
81 * <p/>The rules of the game are as follows:
82 * <p/>At initialization, an absolute maximum authorized number of items
83 * is computed. All item reservations and authorizations are done
85 * <p/>At any given point in time, out of this maximum, a number of items are
86 * permanently reserved for the minimum guaranteed to each current account,
87 * a number of items are set aside for future accounts guarantee reservation,
88 * and the rest is open for dynamic attribution on a first come first serve
91 * <p/>The current strategy is as follows:
93 * The initialization parameters are:<ul>
94 * <li>minimum number of guaranteed accounts: {@code minAccounts}</li>
95 * <li>minimum commitment to new accounts up to minAccounts: {@code minReserve}</li>
96 * <li>maximum commitment to new accounts: {@code maxReserve}</li>
97 * <li>extra number of dynamically allocatable items: {@code extraItems}</li>
100 * <p/>We infer the number of items dedicated to reservation: {@code reservedItems}
101 * That is {@code minReserve * minAccounts}.
103 * <p/>Accounts can ask for a commitment in excess of minReserve. Any reservation
104 * made by an account beyond the minimum is satisfied from extraItems
105 * limited by what's available and maxReserve. When minAccounts have
106 * registered, it is possible that reservedItems is exhausted. New accounts
107 * are then accepted on a best effort basis using extra items exclusively. This
108 * may cause such new accounts to be given a commitment inferior to minReserve,
109 * including zero. It is up to the account to reject the offer and give up
110 * by closing, or to go along with the offer. At this time, we do not try
111 * to raise the commitment made to an account while it is registered.
113 * <p/>During the life of the account, items are allocated first from the set
114 * reserved by this account. If the account is out of reserved items, an attempt
115 * is made at getting the item from extraItems.
117 * <p/>For each account we count the number of items reserved from reservedItems,
118 * reserved from extraItems, allocated from the local reserved items
119 * and allocated from extraItems separately. When an item is released, it is
120 * accounted to extraItems if the account had anything allocated from
121 * extra items, or to the local reserved items.
122 * When an account goes away, the number of items that were reserved from
123 * reserveItems go back to reserveItems and likewise for those coming
124 * from extraItems. This is done rather than giving priority to reserve
125 * items so that the system does not favor reservation beyond its initial
126 * parameters when an account goes away under load.
128 * <p/>When resources are scarce, two modes of operations are available.
131 * <dd>each account keeps its items as long it has a use for them. If
132 * the allocation of a new item is denied for an account, the account just has
133 * to live with it and try again the next time more items are desired.
136 * <dd>Each account releases each item after one use. When allocation
137 * of a new item is denied for an account by reason of item shortage, the
138 * account is placed on a list of eligible accounts. Every time an item is
139 * released, it is re-assigned to the oldest eligible account.
142 * <p/>From an API point of view the difference is not visible: account users
143 * are advised to release items after one use. Release returns the account to
144 * which the item has been re-assigned. If RoundRobin is not used, then
145 * the item is always re-assigned to the account that releases it unless it
146 * is not needed, in which case it returns to the available pool.
147 * So, with round-robin off the following is true:
149 * a.releaseItem() == (a.needs ? a : null);
153 public class ResourceDispatcher {
158 private final static transient Logger LOG = Logger.getLogger(ResourceDispatcher.class.getName());
160 private long extraItems;
161 private long reservedItems;
162 private final long maxReservedPerAccount;
163 private final long minReservedPerAccount;
164 private final long maxExtraPerAccount;
165 private final long minExtraPoolSize;
166 private int nbEligibles;
168 private final String myName;
170 class ClientAccount extends Dlink implements ResourceAccount {
173 * Tells whether this account has any use for extra resources
174 * or not. This feature is required to support roundRobin mode
175 * properly when resources are scarce.
177 private boolean needs;
180 * The number of items reserved for this account that may still be
181 * allocated. This decrements when we grant an item allocation. When
182 * it is <= 0, new items may be obtained from extraItems. If obtained
183 * we still decrement. When an item is released, if this counter is
184 * < 0 we return the item to extra items. This counter gets
185 * incremented either way.
187 private long nbReserved;
190 * The number out of nbReserved that is due back in reservedItems
191 * when this account disappears.
192 * The rest goes back to extraItems.
193 * NOTE: If we go away with items unaccounted for, we take the option
194 * of accounting them as allocated. In other words, that amount is
195 * not returned to its right full item account. That's why we do not
196 * need to keep track of allocated items. The leak is felt
197 * by this allocator. Alternatively we could pretend that the
198 * leaked resources are not ours; but that might break the actual
199 * allocator of the resource if it relies on our accounting.
201 private long fromReservedItems;
204 * Same idea but they have been reserved by reducing the number
205 * of extra items available.
207 private final long fromExtraItems;
210 * The limit for extra items allocation.
211 * When nbReserved is at or below that, extra items cannot be
214 private final long extraLimit;
217 * The external object for which this account manages items.
218 * This is an opaque cookie to us. Whatever code invokes
219 * releaseItem knows what to do with it and the re-assigned item, but
220 * it needs to be told which of its own object got an item assigned.
222 private Object userObject;
225 * Creates a client account with this resource manager.
226 * Not for external use.
227 * @param fromReservedItems
228 * @param fromExtraItems
232 ClientAccount(long fromReservedItems, long fromExtraItems, long extraLimit, Object userObject) {
234 this.nbReserved = fromReservedItems + fromExtraItems;
235 this.fromReservedItems = fromReservedItems;
236 this.fromExtraItems = fromExtraItems;
237 this.extraLimit = -extraLimit;
238 this.userObject = userObject;
245 * <p/>To accelerate return of resources to the global pool, one
246 * may call close() explicitly. Otherwise it is called by
249 * <p/>Calling close() or letting the account be GC'ed while some of the
250 * resources have not been returned is an error, may create a leak and
251 * may display a warning message.
253 public void close() {
257 if ((nbReserved == 0) && (fromReservedItems == 0) && (fromExtraItems == 0)) {
261 if (nbReserved < (fromReservedItems + fromExtraItems)) {
262 // !!! someone just gave up on its resource controller
263 // without returning the resources !
264 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
265 LOG.warning("An account was abandoned with resources still allocated.");
268 // Release whatever we can.
269 if (nbReserved >= fromReservedItems) {
270 releaseExtra(nbReserved - fromReservedItems);
271 releaseReserved(fromReservedItems);
272 } else if (nbReserved > 0) {
273 releaseReserved(nbReserved);
277 releaseReserved(fromReservedItems);
278 releaseExtra(fromExtraItems);
287 * <p/>Will close the account. (close is idempotent).
290 protected void finalize() throws Throwable {
299 public boolean isIdle() {
300 return (nbReserved == fromExtraItems + fromReservedItems);
303 public boolean isEligible() {
308 * Put that account in the queue of accounts eligible to
309 * receive a resource when one becomes available.
311 public void beEligible() {
312 if ((eligibles != null) && !isEligible()) {
318 * Remove that account from the queue of accounts eligible to
319 * receive a resource when one becomes available.
321 public void notEligible() {
322 if ((eligibles != null) && isEligible()) {
327 // An extra item is being granted to this account (by being reassigned
328 // from another account upon release).
329 private void granted() {
331 // In theory, there cannot be an account that should NOT be granted
332 // an item in the eligibles list. For now, check whether the theory
333 // survives observations.
334 // It could happen that the need flag was turned off while this
335 // account was in the eligible list. That's not realy a problem.
336 // Either it will be released immediately, or we could filter
337 // it in mostEligible().
338 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
339 if (nbReserved <= extraLimit) {
340 LOG.warning("An account that should not get an item was found in the eligibles list");
346 // We've been assigned an item. No-longer eligible.
353 public boolean obtainQuantity(long quantity) {
355 if ((nbReserved - quantity) < extraLimit) {
356 // That's asking too much. Denied.
360 if (quantity > nbReserved) {
361 // We need to get some or all of it from the extra items.
362 long toAsk = nbReserved > 0 ? quantity - nbReserved : quantity;
363 long res = holdExtra(toAsk);
366 // Could not get enough. We got nothing.
373 nbReserved -= quantity;
375 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
376 if (nbReserved > fromReservedItems + fromExtraItems) {
377 LOG.severe("Incorrect values after obtaining " + quantity + " : [" + this + "]");
387 public boolean obtainItem() {
389 // Set it for consistency. It will get cleared when
390 // the item is used to satisfy the need.
393 if (nbReserved > 0) {
396 return true; // Its pre-reserved.
399 // This account may deliberately limit the number of extra
400 // items it uses. this translates into a lower limit for
401 // nbReserved when <= 0.
402 if (nbReserved <= extraLimit) {
407 if (holdExtra(1) == 1) { // Need authorization.
413 // We are out of luck but eligible.
421 public void releaseQuantity(long quantity) {
422 if (nbReserved < 0) {
423 releaseExtra(quantity < -nbReserved ? quantity : -nbReserved);
425 nbReserved += quantity;
426 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
427 if (nbReserved > fromReservedItems + fromExtraItems) {
428 LOG.severe("Incorrect values after releasing " + quantity + " : [" + this + "]");
436 public ResourceAccount releaseItem() {
437 if (nbReserved < 0) {
438 if (eligibles == null) {
439 // RoundRobin is OFF either we reuse it or we let
450 // RoundRobin is ON, we compete with others for this item.
453 // Update our eligibility which depends on extraLimit and
454 // whether we have a use for the item or not.
455 if ((nbReserved > extraLimit) && needs) {
459 ClientAccount next = mostEligible();
462 releaseExtra(1); // noone wants it. return to main pool
469 // Since we are (back) in our reserved range, we can't be eligible
473 // In reserved range; we keep using the item if we need it.
485 public void inNeed(boolean needs) {
487 if ((nbReserved < 0) && (nbReserved > extraLimit) && needs) {
497 public Object getUserObject() {
504 public void setUserObject(Object object) {
511 public long getNbReserved() {
518 * <p/>Returns some human-readable status and identity information useful for debugging.
521 public String toString() {
522 return super.toString() + " : needs=" + needs + " nbReserved=" + nbReserved + " fromReservedItems="
523 + fromReservedItems + " fromExtraItems=" + fromExtraItems + " extraLimit=" + extraLimit;
529 * The list of eligible accounts.
531 private Dlist eligibles;
534 * Construct a Fair Resource Allocator with the given parameters:
535 * @param minAccounts The minimum number of client accounts that we want to
536 * guarantee we can handle. <0 means 0
538 * @param minReservedPerAccount The minimum reservation request that we will
539 * always grant to accounts as long as we have less than minAccounts <0 means
541 * @param maxReservedPerAccount The maximum reservation request that we ever
542 * will grant to any given account. <minReservedPerAccount means ==
543 * @param extraItems The total number of items that we will authorize
544 * beyond what has been reserved. <0 means 0.
545 * @param maxExtraPerAccount The maximum number of extra items we will ever
546 * let any given account occupy. <0 or >extraItems means ==extraItems.
547 * @param minExtraPoolSize The number of extra items that can never be
548 * taken out of the extra pool to satisfy a reservation request.
549 * @param roundRobin If true, when there is no items available, all
550 * eligible accounts are put in a FIFO. Accounts release items often, and the
551 * oldest account in the FIFO will get it. If false, accounts always keep
552 * items for as long as they can use them, and there is no FIFO of eligible
553 * accounts. Accounts can obtain new resources only if available at the time
554 * they try to aquire it. RoundRobin is more fair but has more overhead.
555 * Neither mode will cause starvation as long as accounts reserve at least
556 * one item each. RoundRobin is most useful when allocating threads.
558 public ResourceDispatcher(long minAccounts, long minReservedPerAccount, long maxReservedPerAccount, long extraItems, long maxExtraPerAccount, long minExtraPoolSize, boolean roundRobin, String dispatcherName) {
559 if (minAccounts < 0) {
562 if (minReservedPerAccount < 0) {
563 minReservedPerAccount = 0;
565 if (maxReservedPerAccount < minReservedPerAccount) {
566 maxReservedPerAccount = minReservedPerAccount;
568 if (extraItems < 0) {
571 if (minExtraPoolSize < 0) {
572 minExtraPoolSize = 0;
575 if ((maxExtraPerAccount < 0) || (maxExtraPerAccount > extraItems)) {
576 maxExtraPerAccount = extraItems;
579 this.extraItems = extraItems;
580 this.minExtraPoolSize = minExtraPoolSize;
581 this.maxReservedPerAccount = maxReservedPerAccount;
582 this.minReservedPerAccount = minReservedPerAccount;
583 this.reservedItems = minAccounts * minReservedPerAccount;
584 this.maxExtraPerAccount = maxExtraPerAccount;
587 eligibles = new Dlist();
590 this.myName = dispatcherName;
593 private long holdReserved(long req) {
594 if (req > reservedItems) {
597 reservedItems -= req;
601 private void releaseReserved(long nb) {
605 private long holdExtra(long req) {
606 if (req > extraItems) {
613 // Get items from the extra pool but only if there is at least
614 // minExtraPoolSize item
615 // left after that. The goal is to make sure we keep at least one
616 // un-reserved item when granting reserved items from the extra pool.
617 // Thanks to that, even accounts that could not get a single reserved
618 // item still stand a chance to make progress by taking turns using
619 // the one extra item left.
620 private long holdExtraKeepSome(long req) {
621 if (extraItems <= minExtraPoolSize) {
624 long allowed = extraItems - minExtraPoolSize;
633 private void releaseExtra(long nb) {
637 private void newEligible(ClientAccount account) {
639 eligibles.putLast(account);
642 private ClientAccount mostEligible() {
643 if (nbEligibles == 0) {
646 return (ClientAccount) eligibles.getFirst();
649 private void unEligible(ClientAccount account) {
654 // Not synch; it's just a snapshot for trace purposes.
655 public int getNbEligibles() {
660 * Creates and returns a new client account.
662 * @param nbReq the number of reserved items requested (may not be
663 * always granted in full). A negative value is taken to mean 0.
664 * @param maxExtra the number of additional items that this account
665 * authorizes to be allocated in addition to the reserved ones. This
666 * is typically useful if the items are threads and if some accounts
667 * are not re-entrant. Then nbReq would be 1 and maxExtra would be 0.
668 * It is also permitted to have some accounts receive no items at all
669 * ever by setting nbReq and maxExtra both to zero. A negative maxExtra
670 * is taken as meaning no specified limit, in which case an actual limit
671 * may be set silently.
672 * @param userObject An opaque cookie that the account object will return
673 * when requested. This is useful to relate an account returned by
674 * ClientAccount.releaseItem() to an invoking code relevant object.
675 * @return ResourceAccount An account with this allocator.
677 public ResourceAccount newAccount(long nbReq, long maxExtra, Object userObject) {
679 long extra = 0; // reserved from extra pool
680 long reserved = 0; // reserved from reserved pool
682 if (nbReq > maxReservedPerAccount) {
683 nbReq = maxReservedPerAccount;
686 // Anything beyond the minimum comes from extra items if there's
688 if (nbReq > minReservedPerAccount) {
689 extra = holdExtraKeepSome(nbReq - minReservedPerAccount);
690 nbReq = minReservedPerAccount;
693 // Then the minimum comes from reserved items, if we can.
694 reserved = holdReserved(nbReq);
697 // If there's some letf to be had, it means that we're getting
698 // short on reserved items, we'll try to compensate by getting
699 // more items from extra, but the app should start getting rid
700 // of stale accounts if it can.
702 if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
703 LOG.info("Accepting extra account on a best effort basis.");
706 extra += holdExtraKeepSome(nbReq);
707 if (extra + reserved < minReservedPerAccount) {
708 // Even that was not enough to reach our minimal commitment.
709 // The app should realy consider some cleanup.
710 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
711 LOG.warning("[" + myName + "] Accepting extra account with below-minimal commitment:[" + userObject + "]");
716 if ((maxExtra > maxExtraPerAccount) || (maxExtra < 0)) {
717 maxExtra = maxExtraPerAccount;
720 return new ClientAccount(reserved, extra, maxExtra, userObject);