package edu.washington.cs.oneswarm.f2f.datagram; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.logging.Level; import java.util.logging.Logger; public class DatagramRateLimiter { public final static Logger logger = Logger.getLogger(DatagramRateLimiter.class.getName()); protected volatile int availableTokens = 0; protected int maxAvailableTokens = 2 * DatagramConnection.MAX_DATAGRAM_SIZE; protected final ArrayList<DatagramRateLimiter> queues = new ArrayList<DatagramRateLimiter>(); private final Comparator<DatagramRateLimiter> comparator = new Comparator<DatagramRateLimiter>() { @Override public int compare(DatagramRateLimiter o1, DatagramRateLimiter o2) { return o1.getAvailableTokens() - o2.getAvailableTokens(); } }; /** * Add tokens to this bucket. * * @param tokens * The maximum number of tokens to add. * * @return The number of tokens actually added. */ public int refillBucket(int tokens) { int toRefill = Math.min(tokens, maxAvailableTokens - availableTokens); availableTokens += toRefill; if (logger.isLoggable(Level.FINEST)) { logger.finest(toString() + ": " + toRefill + " tokens added, available: " + availableTokens); } return toRefill; } public synchronized void allocateTokens() { logger.finer(toString() + ": allocating tokens"); Collections.sort(queues, comparator); int queueNum = queues.size(); if (queueNum == 0) { logger.finest(toString() + ": no queues, returning"); return; } int fairShare = availableTokens / queueNum; logger.finest(toString() + ": fair share: " + fairShare); for (int i = 0; i < queueNum; i++) { DatagramRateLimiter queue = queues.get(i); int tokensUsed = queue.refillBucket(fairShare); availableTokens -= tokensUsed; queue.allocateTokens(); int queuesLeft = queueNum - (i + 1); if (tokensUsed < fairShare && queuesLeft > 0) { fairShare = availableTokens / queuesLeft; logger.finest(toString() + ": fair share updated: " + fairShare + " available=" + availableTokens + " queues_left=" + queuesLeft); } } } protected synchronized void addQueue(DatagramRateLimiter queue) { queues.add(queue); queue.setTokenBucketSize(maxAvailableTokens); logger.fine(toString() + ": queue added: " + queue.toString()); } protected synchronized void removeQueue(DatagramRateLimiter queue) { queue.transferTokens(this, getAvailableTokens()); queues.remove(queue); logger.fine(toString() + ": queue removed: " + queue.toString()); } public int getAvailableTokens() { return availableTokens; } public int getTokenBucketSize() { return this.maxAvailableTokens; } public void transferTokens(DatagramRateLimiter target, int maxToTransfer) { int amount = target.refillBucket(Math.min(maxToTransfer, availableTokens)); availableTokens -= amount; if (logger.isLoggable(Level.FINEST)) { logger.finest(toString() + ": transfered " + amount + " tokens, target=" + target.toString()); } } public boolean isFull() { return availableTokens == maxAvailableTokens; } public void setTokenBucketSize(int tokens) { this.maxAvailableTokens = tokens; } @Override public String toString() { return "RateLimitedQueue, tokens: " + availableTokens + "/" + maxAvailableTokens; } }