package freenet.support; import freenet.support.Logger.LogLevel; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * Token bucket. Can be used for e.g. bandwidth limiting. * Tokens are added once per tick. */ @Deprecated public class TokenBucket { private static boolean logMINOR; static { LoggerHook.registerClass(TokenBucket.class); } 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; if(current > max) { Logger.error(this, "initial value ("+current+") > max ("+max+") in "+this, new Exception("error")); current = max; } this.nanosPerTick = nanosPerTick; long now = System.currentTimeMillis(); this.timeLastTick = NANOSECONDS.convert(now, MILLISECONDS); if(nanosPerTick <= 0) throw new IllegalArgumentException(); if(max <= 0) throw new IllegalArgumentException(); } /** * 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) { if(tokens < 0) throw new IllegalArgumentException("Can't grab negative tokens: "+tokens); if(logMINOR) Logger.minor(this, "instant grab: "+tokens+" current="+current+" max="+max); addTokens(); if(logMINOR) Logger.minor(this, "instant grab: "+tokens+" current="+current+" max="+max); if(current >= tokens) { current -= tokens; return true; } else { return false; } } /** * Try to grab some tokens; if there aren't enough, grab all of them. Never block. * @param tokens The number of tokens to grab. * @return The number of tokens grabbed. */ public synchronized long partialInstantGrab(long tokens) { if(tokens < 0) throw new IllegalArgumentException("Can't grab negative tokens: "+tokens); if(logMINOR) Logger.minor(this, "instant grab: "+tokens+" current="+current+" max="+max); addTokens(); if(logMINOR) Logger.minor(this, "instant grab: "+tokens+" current="+current+" max="+max); if(current >= tokens) { current -= tokens; return tokens; } else { tokens = current; current = 0; return tokens; } } /** * 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) { if(tokens < 0) throw new IllegalArgumentException("Can't grab negative tokens: "+tokens); if(logMINOR) Logger.minor(this, "forceGrab("+tokens+")"); addTokens(); current -= tokens; if(logMINOR) Logger.minor(this, "Removed tokens, balance now "+current); } public synchronized long count() { return current; } /** * Get the current number of available tokens. */ public synchronized long getCount() { addTokens(); return current; } protected long offset() { return 0; } public synchronized void blockingGrab(long tokens) { if(tokens < 0) throw new IllegalArgumentException("Can't grab negative tokens: "+tokens); logMINOR = Logger.shouldLog(LogLevel.MINOR, this); if(logMINOR) Logger.minor(this, "Blocking grab: "+tokens); if(tokens < max) innerBlockingGrab(tokens); else { for(int i=0;i<tokens;i+=max) { innerBlockingGrab(Math.min(tokens, max)); } } } /** * Grab a bunch of tokens. Block if necessary. * @param tokens The number of tokens to grab. */ public synchronized void innerBlockingGrab(long tokens) { if(tokens < 0) throw new IllegalArgumentException("Can't grab negative tokens: "+tokens); if(logMINOR) Logger.minor(this, "Inner blocking grab: "+tokens); addTokens(); if(logMINOR) Logger.minor(this, "current="+current); current -= tokens; if(current >= 0) { if(logMINOR) Logger.minor(this, "Got tokens instantly, current="+current); return; } else { if(logMINOR) Logger.minor(this, "Blocking grab removed tokens, current="+current+" - will have to wait because negative..."); } long minDelayNS = nanosPerTick * (-current); long minDelayMS = MILLISECONDS.convert(minDelayNS + MILLISECONDS.toNanos(1) - 1, NANOSECONDS); long now = System.currentTimeMillis(); long wakeAt = now + minDelayMS; if(logMINOR) Logger.minor(this, "Waking in "+minDelayMS+" millis"); while(true) { now = System.currentTimeMillis(); int delay = (int) Math.min(Integer.MAX_VALUE, wakeAt - now); if(delay <= 0) break; if(logMINOR) Logger.minor(this, "Waiting "+delay+"ms"); try { wait(delay); } catch (InterruptedException e) { // Go around the loop again. } } if(logMINOR) Logger.minor(this, "Blocking grab finished: current="+current); } public synchronized void recycle(long tokens) { if(tokens < 0) throw new IllegalArgumentException("Can't recycle negative tokens: "+tokens); current += tokens; if(current > max) current = max; } /** * Change the number of nanos per tick. * @param nanosPerTick The new number of nanos per tick. */ public synchronized void changeNanosPerTick(long nanosPerTick) { if(nanosPerTick <= 0) throw new IllegalArgumentException(); // Synchronize up first, using the old nanosPerTick. addTokens(); this.nanosPerTick = nanosPerTick; if(nanosPerTick < this.nanosPerTick) notifyAll(); } public synchronized void changeBucketSize(long newMax) { if(newMax <= 0) throw new IllegalArgumentException(); max = newMax; addTokens(); } 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. addTokensNoClip(); if(nanosPerTick < this.nanosPerTick) notifyAll(); this.nanosPerTick = nanosPerTick; this.max = newMax; if(current > max) current = max; } public synchronized void addTokens() { addTokensNoClip(); if(current > max) current = max; if(logMINOR) Logger.minor(this, "addTokens: Clipped, current="+current); } /** * Update the number of tokens according to elapsed time. */ public synchronized void addTokensNoClip() { long add = tokensToAdd(); current += add; timeLastTick += add * nanosPerTick; if(logMINOR) Logger.minor(this, "addTokensNoClip: Added "+add+" tokens, current="+current); // 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 = NANOSECONDS.convert(System.currentTimeMillis(), MILLISECONDS); if(timeLastTick > nowNS) { System.err.println("CLOCK SKEW DETECTED! CLOCK WENT BACKWARDS BY AT LEAST "+TimeUtil.formatTime(MILLISECONDS.convert(timeLastTick - nowNS, NANOSECONDS), 2, true)); System.err.println("FREENET WILL BREAK SEVERELY IF THIS KEEPS HAPPENING!"); Logger.error(this, "CLOCK SKEW DETECTED! CLOCK WENT BACKWARDS BY AT LEAST "+TimeUtil.formatTime(MILLISECONDS.convert(timeLastTick - nowNS, NANOSECONDS), 2, true)); timeLastTick = nowNS; return 0; } long nextTick = timeLastTick + nanosPerTick; if(nextTick > nowNS) { return 0; } if(nextTick + nanosPerTick > nowNS) { return 1; } return (nowNS - nextTick) / nanosPerTick; } public synchronized long getNanosPerTick() { return nanosPerTick; } }