/* * Copyright (c) 2003-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.pipe.reliable; import net.jxta.impl.util.TimeUtils; public class AdaptiveFlowControl extends FlowControl { static final int DEFAULT_RWINDOW = 2; /** * global state. */ private int MAX_TENSION = 3; private int tension = 0; private long nextRwinChange = TimeUtils.timeNow(); private long prevAveRTT = 10 * TimeUtils.ASECOND; private int RINGSZ = 8; private long[] ackTimeRing = new long[RINGSZ]; private int currAckRingOff = 0; private int nbSamples = 0; private long currAvePeriod = 1; private long prevAvePeriod = 1; // not in use yet. private long periodRangeSlow = Long.MAX_VALUE; // not in use, yet. private long periodRangeFast = (periodRangeSlow / 3) * 2; /** * Current recommended rwindow. */ private volatile int rwindow = 0; /** * state of the currentAck being processed */ // Accum of acked packets private int numberACKed = 0; // Accum of missing packets private int numberMissing = 0; // Time this ACK arrived private long currACKTime = 0; // These variables are used to evaluate the longest run // of consecutive holes in the sack list. That is consecutive // seqnums from the retrQ that are not being acknowleged, // followed by an acknowleged one. private int prevHole = -2; private int btbHoles = 0; private int maxHoleRun = 0; /** * Constructs an adaptive flow control module with an initial rwindow of * DEFAULT_RWINDOW. */ public AdaptiveFlowControl() { this(DEFAULT_RWINDOW); } /** * @param rwindow Use this value as the initial value (not recommended * except for experimental purposes. */ public AdaptiveFlowControl(int rwindow) { this.currACKTime = TimeUtils.timeNow(); this.rwindow = rwindow; } /** * {@inheritDoc} */ @Override public int getRwindow() { return rwindow; } /** * {@inheritDoc} */ @Override public void ackEventBegin() { currACKTime = TimeUtils.timeNow(); numberACKed = 0; numberMissing = 0; maxHoleRun = 0; // Note the currently open holerun carries over from the prev ACK. // So, we leave prevHole and btbHoles alone. } /** * {@inheritDoc} */ @Override public void packetACKed(int seqnum) { if (btbHoles > maxHoleRun) { maxHoleRun = btbHoles; } btbHoles = 0; prevHole = -2; numberACKed++; } /** * {@inheritDoc} */ @Override public void packetMissing(int seqnum) { if (seqnum != prevHole + 1) { // End of run, begining of next if (btbHoles > maxHoleRun) { maxHoleRun = btbHoles; } btbHoles = 0; } btbHoles++; prevHole = seqnum; numberMissing++; } boolean fastMode = true; int takeAchance = 0; /** * {@inheritDoc} */ @Override public int ackEventEnd(int rQSize, long aveRTT, long lastRTT) { // Compute average ack rate. If nothing was acked by this // ack msg, consider it a bad sign as far as ack rates go: as good // as no ack at all. // Even if a few ack messages where lost, the current event // encapsulate all the acks we missed. Count each of them // as one individual ack for the purpose of rate computation: // we want to count the messages the other side has received // not the number of ack messages that found their way back. if (numberACKed > 0) { for (int a = 0; a < numberACKed; ++a) { // Adds a new sample to the ring. Returns the new average // sample period. Once the ring is filled, the average is // computed by substracting the sample at the current // offset (the oldest) from the new sample that replaces // it, and dividing by the ring size (10). During the // first round, we use the first sample, so precision is // poorer. long oldest = ackTimeRing[currAckRingOff]; if (nbSamples < RINGSZ) { // make a fake (very) oldest sample if there is nothing yet. if (nbSamples == 0) { ackTimeRing[0] = currACKTime / 2; } ++nbSamples; oldest = ackTimeRing[0]; } ackTimeRing[currAckRingOff++] = currACKTime; if (currAckRingOff == RINGSZ) { currAckRingOff = 0; } prevAvePeriod = currAvePeriod; currAvePeriod = (currACKTime - oldest) / nbSamples; } } // Compute rwindow. It should keep oscillating around // the best value. // Up to a certain point, the higher we keep rwindow the more // we keep all the bandwidth utilized. Beyond that point we have // it just serves to create congestion. int oldSize = rwindow; if (TimeUtils.toRelativeTimeMillis(nextRwinChange) < 0) { if (maxHoleRun < 4) { if (numberACKed > 0) { if (currAvePeriod < periodRangeFast) { // All is well: new rate record. We can // push some more. and adjust the // expected rate range towards speed. periodRangeFast = currAvePeriod; periodRangeSlow = (periodRangeFast * 3) / 2; prevAveRTT = aveRTT; tension = 0; } else { // If rate is not up and RTT has // increased by one inter-ack period or more // since the last time we took the mark, it // looks like one or more packet just had to // wait its turn. So packets are being // buffered, which does not do any // good. Refrain from pushing under these // conditions. Wait for a more favorable time. // This compares the change in RTT with the period // and gives us a badness index from 0 to 10 * n // Beyond 20 or so, we start getting worried. // The hairy formula below compensates for non // linearity of the rtt_diff/period ratio with // period. The formulat compresses the scale towards // a period of 0. There is no compression at a period // of around 100 and maximum compression at 0. // To make the index less sensitive for low periods, // increase the compression ratio. int compressionRatio100 = 90; int pivot = 100; long period = currAvePeriod <= 0 ? 1 : currAvePeriod; long backupSign = (10 * pivot * (aveRTT - prevAveRTT)) / (pivot * period + Math.max((compressionRatio100 * (pivot * period - period * period)) / 100, 0)); if (backupSign > 18) { // if detect a speed increase, we'll reset our // idea of the normal RTT for next time. But we // will drop rwindow tension for now. rwindow--; tension = MAX_TENSION; // The first time this happens, it's the end of fast // mode. fastMode = false; } else if ((backupSign < 1) && (currAvePeriod >= periodRangeSlow)) { if (tension >= MAX_TENSION) { // May be we should give it another chance // and nudge it just a little. On very lossy // links we may end-up with no congestion // at all but stuck at low speed because // we have stopped believing in speed increase. if (takeAchance++ > 10) { takeAchance = 0; tension--; } } } else { takeAchance = 0; } } if (tension < MAX_TENSION) { tension++; rwindow++; } } else { // Carefull, the other side did not ack anything // it is stuck on a missing packet...better slow down tension = MAX_TENSION; } } else { // We saturated the pipe. We need to slow down // arbitrarily without changing our idea of speed, // so that the correlation between speed and // rwindow is shifted towards a smaller // rwindow. rwindow -= (MAX_TENSION + 1); prevAveRTT = aveRTT; tension = MAX_TENSION; // The first time this happens, it's the end of fast // mode. fastMode = false; } if (rwindow > rQSize) { rwindow = rQSize; } if (rwindow < 2) { rwindow = 2; } if (oldSize != rwindow) { if (fastMode && (tension < MAX_TENSION)) { nextRwinChange = TimeUtils.toAbsoluteTimeMillis(lastRTT / 10); } else { nextRwinChange = TimeUtils.toAbsoluteTimeMillis(aveRTT * 2); } } } return rwindow; } }