package ch.cyberduck.core.io; import ch.cyberduck.core.io.service.BandwidthThrottleService; import org.apache.log4j.Logger; /** * Limits throughput of a stream to at most N bytes per T seconds. Mutable and * thread-safe.<p> * <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> * <p/> * 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> * <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> * <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> * * @version $Id: BandwidthThrottle.java 5484 2009-10-18 19:58:40Z dkocher $ */ public class BandwidthThrottle implements BandwidthThrottleService { protected static Logger log = Logger.getLogger(BandwidthThrottle.class); /** * 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. * @uml.property name="_bytesPerTick" */ private volatile int _bytesPerTick; /** * Whether or not we're only allowing bandwidth to be used every other second. * @uml.property name="_switching" */ private volatile boolean _switching = false; /** * The number of bytes remaining in this window. * @uml.property name="_availableBytes" */ private int _availableBytes; /** * The system time when the window is reset so more bytes can be sent. * @uml.property name="_nextTickTime" */ 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) { this.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) { this.setRate(bytesPerSecond); this.setSwitching(switching); } /** * Bytes per second allowed * @uml.property name="_rate" */ private float _rate = UNLIMITED; /* (non-Javadoc) * @see ch.cyberduck.core.io.BandwidthThrottleService#setRate(float) */ public void setRate(float bytesPerSecond) { log.debug("setRate:"+bytesPerSecond); if(bytesPerSecond < 0) { _rate = UNLIMITED; } else { _rate = bytesPerSecond; _bytesPerTick = (int) (bytesPerSecond / TICKS_PER_SECOND); } if(_switching) { this.fixBytesPerTick(true); } } /* (non-Javadoc) * @see ch.cyberduck.core.io.BandwidthThrottleService#getRate() */ public float getRate() { return _rate; } /* (non-Javadoc) * @see ch.cyberduck.core.io.BandwidthThrottleService#setSwitching(boolean) */ public void setSwitching(boolean switching) { log.debug("setSwitching:"+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; } /* (non-Javadoc) * @see ch.cyberduck.core.io.BandwidthThrottleService#request(int) */ synchronized public int request(int desired) { if(UNLIMITED == _rate) { return 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 { log.info("Throttling bandwidth for "+(_nextTickTime - now) +" milliseconds"); Thread.sleep(_nextTickTime - now); } catch(InterruptedException e) { log.error(e.getMessage()); } } } /** * 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; } } } }