package com.limegroup.gnutella.util; /** * Limits throughput of a stream to at most N bytes per T seconds. Mutable and * thread-safe.<p> * * In the following example, <tt>throttle</tt> is used to send the contents of * <tt>buf</tt> to <tt>out</tt> at no more than <tt>N/T</tt> bytes per second: * <pre> * BandwidthThrottle throttle=new BandwidthThrottle(N, T); * OutputStream out=...; * byte[] buf=...; * for (int i=0; i<buf.length; ) { * int allowed=throttle.request(buf.length-i); * out.write(buf, i, allowed); * i+=allowed; * } * </pre> * * This class works by allowing exactly N bytes to be sent every T seconds. If * the number of bytes for a given window have been exceeded, subsequent calls * to request(..) will block. The default value of T is 100 milliseconds. * Smaller window values T allow fairer bandwidth sharing and less noticeable * pauses but may decrease efficiency slightly.<p> * * Note that throttles are <i>not</i> cumulative. In the future, this may allow * enable fancier control. Also, BandwidthThrottle may be able delegate to * other throttles. This would allow, for example, a 15 KB/s Gnutella messaging * throttle, with no more than 10 KB/s devoted to uploads.<p> * * This implementation is based on the <a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/freenet/freenet/src/freenet/support/io/Bandwidth.java">Bandwidth</a> class from * the Freenet project. It has been simplified and better documented.<p> */ public class BandwidthThrottle { /** The number of windows per second. */ private static final int TICKS_PER_SECOND = 10; /** The value of T, in milliseconds. */ private static final int MILLIS_PER_TICK = 1000 / TICKS_PER_SECOND; /** The bytes to send per tick. Modified by setThrottle. */ private volatile int _bytesPerTick; /** * Whether or not we're only allowing bandwidth to be used every other * second. */ private volatile boolean _switching = false; /** The number of bytes remaining in this window. */ private int _availableBytes; /** The system time when the window is reset so more bytes can be sent. */ private long _nextTickTime; /** * Creates a new bandwidth throttle at the given throttle rate. * The default windows size T is used. The bytes per windows N * is calculated from bytesPerSecond. * * @param bytesPerSecond the limits in bytes (not bits!) per second * (not milliseconds!) */ public BandwidthThrottle(float bytesPerSecond) { setRate(bytesPerSecond); } /** * Creates a new bandwidth throttle at the given throttle rate, * only allowing bandwidth to be used every other second if * switching is true. * The default windows size T is used. The bytes per windows N * is calculated from bytesPerSecond. * * @param bytesPerSecond the limits in bytes (not bits!) per second * (not milliseconds!) * @param switching true if we should only allow bandwidth to be used * every other second. */ public BandwidthThrottle(float bytesPerSecond, boolean switching) { setRate(bytesPerSecond); setSwitching(switching); } /** * Sets the throttle to the given throttle rate. The default windows size * T is used. The bytes per windows N is calculated from bytesPerSecond. * * @param bytesPerSecond the limits in bytes (not bits!) per second * (not milliseconds!) */ public void setRate(float bytesPerSecond) { _bytesPerTick = (int)((float)bytesPerSecond / TICKS_PER_SECOND); if(_switching) fixBytesPerTick(true); } /** * Sets whether or not this throttle is switching bandwidth on/off. */ public void setSwitching(boolean switching) { if(_switching != switching) fixBytesPerTick(switching); _switching = switching; } /** * Modifies bytesPerTick to either be double or half of what it was. * This is necessary because of the 'switching', which can effectively * reduce or raise the amount of data transferred. */ private void fixBytesPerTick(boolean raise) { int newBytesPerTick = _bytesPerTick; if(raise) newBytesPerTick *= 2; else newBytesPerTick /= 2; if(newBytesPerTick < 0) // overflowed? newBytesPerTick = Integer.MAX_VALUE; _bytesPerTick = newBytesPerTick; } /** * Blocks until the caller can send at least one byte without violating * bandwidth constraints. Records the number of byte sent. * * @param desired the number of bytes the caller would like to send * @return the number of bytes the sender is expected to send, which * is always greater than one and less than or equal to desired */ synchronized public int request(int desired) { waitForBandwidth(); int result = Math.min(desired, _availableBytes); _availableBytes -= result; return result; } /** Waits until data is _availableBytes. */ private void waitForBandwidth() { for (;;) { long now = System.currentTimeMillis(); updateWindow(now); if (_availableBytes != 0) break; try { Thread.sleep(_nextTickTime - now); } catch (InterruptedException e) { //TODO: propogate } } } /** Updates _availableBytes and _nextTickTime if possible. */ private void updateWindow(long now) { if (now >= _nextTickTime) { if(!_switching || ((now/1000)%2)==0) { _availableBytes = _bytesPerTick; _nextTickTime = now + MILLIS_PER_TICK; } else { _availableBytes = 0; // the next tick time is the time we'll hit // the next second. long diff = 1000 - (now % 1000); _nextTickTime = now + diff; } } } //Tests: see core/com/.../tests/BandwidthThrottleTest }