Author: toad
Date: 2006-07-07 14:11:58 +0000 (Fri, 07 Jul 2006)
New Revision: 9480
Added:
trunk/freenet/src/freenet/support/DoubleTokenBucket.java
trunk/freenet/src/freenet/support/TokenBucket.java
Log:
Add untested TokenBucket and DoubleTokenBucket classes.
TokenBucket is a simple token bucket.
DoubleTokenBucket is a token bucket with the ability to limit the number of
tokens taken forcibly. The point with this is to allow some throttled traffic
through even if the non-throttled traffic maxes out the limit.
Added: trunk/freenet/src/freenet/support/DoubleTokenBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/DoubleTokenBucket.java 2006-07-06
20:20:06 UTC (rev 9479)
+++ trunk/freenet/src/freenet/support/DoubleTokenBucket.java 2006-07-07
14:11:58 UTC (rev 9480)
@@ -0,0 +1,65 @@
+package freenet.support;
+
+/**
+ * A TokenBucket variant which provides the following constraint:
+ * Forced token grabs may not use more than M tokens out of the total N.
+ *
+ * In other words:
+ * A classic token bucket keeps a counter, "current", which must not exceed
the max, to
+ * which we add one token every few millis.
+ *
+ * We also keep another counter, unblockableCount, with a separate maximum,
+ * unblockableMax. Whenever a token is added to current by the clock, one is
removed
+ * from unblockableCount (but only if it is >0). Whenever an unblockable
request to
+ * remove tokens comes in, after we update unblockableCount, we calculate how
many
+ * tokens we can add to unblockableCount without exceeding the maximum, and
only allow
+ * that many to be removed from counter.
+ */
+public class DoubleTokenBucket extends TokenBucket {
+
+ private long maxForced;
+ private long curForced;
+
+ /**
+ * Create a DoubleTokenBucket.
+ * @param max The maximum size of the bucket, in tokens.
+ * @param nanosPerTick The number of nanoseconds between ticks.
+ *
+ */
+ public DoubleTokenBucket(long max, long nanosPerTick, long
initialValue, long maxForced) {
+ super(max, nanosPerTick, initialValue);
+ this.maxForced = maxForced;
+ this.curForced = 0;
+ }
+
+ // instantGrab is unchanged.
+
+ // Major changes to forceGrab ! This is where it happens.
+
+ public synchronized void forceGrab(long tokens) {
+ addTokens();
+ long thisMax = maxForced - curForced;
+ if(tokens > thisMax) tokens = thisMax;
+ curForced += tokens;
+ current -= tokens;
+ if(current > max) current = max;
+ if(curForced > maxForced) curForced = maxForced;
+ }
+
+ // blockingGrab is unchanged
+
+ public synchronized void changeSizeOfBuckets(long newMax, long
newMaxForced) {
+ changeBucketSize(newMax);
+ this.maxForced = newMaxForced;
+ if(curForced > maxForced) curForced = maxForced;
+ }
+
+ public synchronized void addTokens() {
+ long add = tokensToAdd();
+ current += add;
+ curForced -= add;
+ if(curForced < 0) curForced = 0;
+ timeLastTick += add * nanosPerTick;
+ }
+
+}
Added: trunk/freenet/src/freenet/support/TokenBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/TokenBucket.java 2006-07-06 20:20:06 UTC
(rev 9479)
+++ trunk/freenet/src/freenet/support/TokenBucket.java 2006-07-07 14:11:58 UTC
(rev 9480)
@@ -0,0 +1,139 @@
+package freenet.support;
+
+/**
+ * Token bucket. Can be used for e.g. bandwidth limiting.
+ * Tokens are added once per tick.
+ */
+public class TokenBucket {
+
+ protected long current;
+ protected long max;
+ protected long timeLastTick;
+ protected long nanosPerTick;
+
+ /**
+ * Create a token bucket.
+ * @param max The maximum size of the bucket, in tokens.
+ * @param nanosPerTick The number of nanoseconds between ticks.
+ */
+ public TokenBucket(long max, long nanosPerTick, long initialValue) {
+ this.max = max;
+ this.current = initialValue;
+ this.nanosPerTick = nanosPerTick;
+ this.timeLastTick = System.currentTimeMillis();
+ }
+
+ /**
+ * Either grab a bunch of tokens, or don't. Never block.
+ * @param tokens The number of tokens to grab.
+ * @return True if we could acquire the tokens.
+ */
+ public synchronized boolean instantGrab(long tokens) {
+ addTokens();
+ if(current > tokens) {
+ current -= tokens;
+ if(current > max) current = max;
+ return true;
+ } else {
+ if(current > max) current = max;
+ return false;
+ }
+ }
+
+ /**
+ * Remove tokens, without blocking, even if it causes the balance to go
negative.
+ * @param tokens The number of tokens to remove.
+ */
+ public synchronized void forceGrab(long tokens) {
+ addTokens();
+ current -= tokens;
+ if(current > max) current = max;
+ }
+
+ /**
+ * Get the current number of available tokens.
+ */
+ public synchronized long count() {
+ return current;
+ }
+
+ /**
+ * Grab a bunch of tokens. Block if necessary.
+ * @param tokens The number of tokens to grab.
+ */
+ public synchronized void blockingGrab(long tokens) {
+ while(true) {
+ addTokens();
+ if(current > tokens) {
+ current -= tokens;
+ if(current > max) current = max;
+ return;
+ } else {
+ if(current > 0) {
+ tokens -= current;
+ current = 0;
+ }
+ }
+ long minDelayNS = nanosPerTick * Math.min(tokens,
max/2);
+ long minDelayMS = minDelayNS / 1000 + (minDelayNS %
1000 == 0 ? 0 : 1);
+ try {
+ wait(minDelayMS);
+ } catch (InterruptedException e) {
+ // Go around the loop again.
+ }
+ }
+ }
+
+ /**
+ * Change the number of nanos per tick.
+ * @param nanosPerTick The new number of nanos per tick.
+ */
+ public synchronized void changeNanosPerTick(long nanosPerTick) {
+ // Synchronize up first, using the old nanosPerTick.
+ if(nanosPerTick <= 0) throw new IllegalArgumentException();
+ addTokens();
+ this.nanosPerTick = nanosPerTick;
+ if(nanosPerTick < this.nanosPerTick)
+ notifyAll();
+ }
+
+ public synchronized void changeBucketSize(long newMax) {
+ if(newMax <= 0) throw new IllegalArgumentException();
+ addTokens();
+ max = newMax;
+ if(current > max) current = max;
+ }
+
+ public synchronized void changeNanosAndBucketSize(long nanosPerTick,
long newMax) {
+ if(nanosPerTick <= 0) throw new IllegalArgumentException();
+ if(newMax <= 0) throw new IllegalArgumentException();
+ // Synchronize up first, using the old nanosPerTick.
+ addTokens();
+ if(nanosPerTick < this.nanosPerTick)
+ notifyAll();
+ this.nanosPerTick = nanosPerTick;
+ this.max = newMax;
+ if(current > max) current = max;
+ }
+
+ /**
+ * Update the number of tokens according to elapsed time.
+ */
+ public synchronized void addTokens() {
+ long add = tokensToAdd();
+ current += add;
+ timeLastTick += add * nanosPerTick;
+ // Deliberately do not clip to size at this point; caller must
do this, but it is usually beneficial for the caller to do so.
+ }
+
+ synchronized long tokensToAdd() {
+ long nowNS = System.currentTimeMillis() * 1000;
+ long nextTick = timeLastTick + nanosPerTick;
+ if(nextTick < nowNS) return 0;
+ if(nextTick + nanosPerTick > nowNS) {
+ timeLastTick = nextTick;
+ return 1;
+ }
+ return (nowNS - nextTick) / nanosPerTick;
+ }
+}