/*
* Dijjer - A Peer to Peer HTTP Cache
* Copyright (C) 2004,2005 Change.Tv, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package freenet.io.xfer;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
public class PacketThrottle {
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
protected static final double PACKET_DROP_DECREASE_MULTIPLE = 0.875;
protected static final double PACKET_TRANSMIT_INCREMENT = (4 * (1 - (PACKET_DROP_DECREASE_MULTIPLE * PACKET_DROP_DECREASE_MULTIPLE))) / 3;
protected static final double SLOW_START_DIVISOR = 3.0;
protected static final long MAX_DELAY = 1000;
protected static final long MIN_DELAY = 1;
public static final String VERSION = "$Id: PacketThrottle.java,v 1.3 2005/08/25 17:28:19 amphibian Exp $";
public static final long DEFAULT_DELAY = 200;
private long _roundTripTime = 500, _totalPackets, _droppedPackets;
/** The size of the window, in packets.
* Window size must not drop below 1.0. Partly this is because we need to be able to send one packet, so it is a logical lower bound.
* But mostly it is because of the non-slow-start division by _windowSize! */
private float _windowSize = 2;
private final int PACKET_SIZE;
private boolean slowStart = true;
public PacketThrottle(int packetSize) {
PACKET_SIZE = packetSize;
}
public synchronized void setRoundTripTime(long rtt) {
_roundTripTime = Math.max(rtt, 10);
if(logMINOR) Logger.minor(this, "Set round trip time to "+rtt+" on "+this);
}
public synchronized void notifyOfPacketsLost(int numPackets) {
if (numPackets <= 0) {
throw new IllegalArgumentException("Reported loss is zero or negative");
}
_droppedPackets += numPackets;
_totalPackets += numPackets;
_windowSize *= Math.pow(PACKET_DROP_DECREASE_MULTIPLE, numPackets);
if (_windowSize < 1.0F) {
_windowSize = 1.0F;
}
slowStart = false;
if (logMINOR) {
Logger.minor(this, "notifyOfPacketsLost(): " + this);
}
}
/**
* Notify the throttle that a packet was transmitted successfully. We will increase the window size.
* @param maxWindowSize The maximum window size. This should be at least twice the largest window
* size actually seen in flight at any time so far. We will ensure that the throttle's window size
* does not get bigger than this. This works even for new packet format, and solves some of the
* problems that RFC 2861 does.
*/
public synchronized void notifyOfPacketAcknowledged(double maxWindowSize) {
_totalPackets++;
// If we didn't use the whole window, shrink the window a bit.
// This is similar but not identical to RFC2861
// See [freenet-dev] Major weakness in our current link-level congestion control
int windowSize = (int)getWindowSize();
if(slowStart) {
if(logMINOR) Logger.minor(this, "Still in slow start");
_windowSize += _windowSize / SLOW_START_DIVISOR;
// Avoid craziness if there is lag in detecting packet loss.
if(_windowSize > maxWindowSize) slowStart = false;
// Window size must not drop below 1.0. Partly this is because we need to be able to send one packet, so it is a logical lower bound.
// But mostly it is because of the non-slow-start division by _windowSize!
if(_windowSize < 1.0F) _windowSize = 1.0F;
} else {
_windowSize += (PACKET_TRANSMIT_INCREMENT / _windowSize);
}
// Ensure that we the window size does not grow dramatically larger than the largest window
// that has actually been in flight at one time.
if(_windowSize > maxWindowSize)
_windowSize = (float) maxWindowSize;
if(_windowSize > (windowSize + 1))
notifyAll();
if(logMINOR)
Logger.minor(this, "notifyOfPacketAcked(): "+this);
}
/** Only used for diagnostics. We actually maintain a real window size. So we don't
* need lots of sanity checking here. */
public synchronized long getDelay() {
// return (long) (_roundTripTime / _simulatedWindowSize);
return Math.max(MIN_DELAY, (long) (_roundTripTime / _windowSize));
}
@Override
public synchronized String toString() {
return Double.toString(getBandwidth()) + " k/sec, (w: "
+ _windowSize + ", r:" + _roundTripTime + ", d:"
+ (((float) _droppedPackets / (float) _totalPackets)) + ") total="+_totalPackets+" : "+super.toString();
}
public synchronized long getRoundTripTime() {
return _roundTripTime;
}
public synchronized double getWindowSize() {
return Math.max(1.0, _windowSize);
}
/**
* returns the number of bytes-per-second in the transmition link (?).
* FIXME: Will not return more than 1M/s due to MIN_DELAY in getDelay().
*/
public synchronized double getBandwidth() {
//PACKET_SIZE=1024 [bytes?]
//1000 ms/sec
return ((PACKET_SIZE * 1000.0 / getDelay()));
}
public synchronized void maybeDisconnected() {
notifyAll();
}
}