package com.limegroup.gnutella.connection;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.util.BucketQueue;
/**
* A message queue that prioritizes messages. These are intended to be
* heterogenous, i.e., to only contain one type of message at a time, though
* that is not strictly enforced. Message are preferenced as follows:
*
* <ol>
* <li>QueryReply: messages with low GUID volume are preferred, i.e., GUID's
* for which few replies have already been routed.
* <li>PingReply: messages with high hops [sic] are preferred, since they
* contain addresses of hosts less likely to be in your horizon.
* <li>Others: messages with low hops are preferred, since they have travelled
* down fewer redundant paths and have received fewer responses.
* </ol>
*
* Then, within any given priority level, newer messages are preferred to
* older ones (LIFO).<p>
*
* Currently this is implemented with a BucketQueue, which provides LIFO
* ordering within any given bucket. BinaryHeap could make sense for
* QueryReply's, but the replacement policy is undefined if the queue
* fills up.
*/
public class PriorityMessageQueue extends AbstractMessageQueue {
/** One priority level for each hop. For query replies, we break reply
* volumes into this many buckets. You could use different numbers of
* priorities according to the type of message, but this is convenient. */
private static final int PRIORITIES=8;
private BucketQueue _queue;
/**
* @param cycle the number of messages to return per cycle, i.e., between
* calls to resetCycle. This is used to tweak the ratios of various
* message types.
* @param timeout the max time to keep queued messages, in milliseconds.
* Set this to Integer.MAX_VALUE to avoid timeouts.
* @param capacity the maximum number of elements this can store.
*/
public PriorityMessageQueue(int cycle,
int timeout,
int capacity) {
super(cycle, timeout);
//Note that this allocates PRIORITIES*capacity storage.
this._queue=new BucketQueue(PRIORITIES, capacity);
}
protected Message addInternal (Message m) {
return (Message)_queue.insert(m, priority(m));
}
/** Calculates a m's priority according to its message type. Larger values
* correspond to higher priorities. */
private static final int priority(Message m) {
if (m instanceof QueryReply)
return priority((QueryReply)m); //Prefer low GUID volume
else if (m instanceof PingReply)
return bound(m.getHops()); //Prefer high hops
else
return bound(PRIORITIES-1-m.getHops()); //Prefer low hops
}
/** Picks a priority from 0 to PRIORITIES-1 roughly according to m's GUID
* volume, i.e., m.getPriority (). */
private static final int priority(QueryReply m) {
//The distribution of reply volumes has a long tale, with most GUID's
//having a moderate number of results but a few GUID's having 400KB+
//results. This suggests calculating the priority from the logarithm of
//the reply volume. While this scheme may result in equal numbers of
//messages in each bucket, it does not sufficiently distinguish between
//high volume replies, which is the most important case. Hence the
//following algorithm. See ConnectionManager.MAX_REPLY_ROUTE_BYTES.
int volume=m.getPriority();
if (volume==0) //No replies
return 7;
else if (volume<1000) //10 or fewer replies
return 6;
else if (volume<5000) //50 or fewer replies
return 5;
else if (volume<10000) //100 or fewer replies
return 4;
else if (volume<20000) //200 or fewer replies
return 3;
else if (volume<30000) //300 or fewer replies
return 2;
else if (volume<40000) //400 or fewer replies
return 1;
else
return 0; //Anything else!
}
/** Ensures that x a valid priority. */
private static final int bound(int priority) {
if (priority<0)
return 0;
else if (priority>=PRIORITIES)
return PRIORITIES-1;
else
return priority;
}
protected Message removeNextInternal() {
if (_queue.isEmpty())
return null;
else
return (Message)_queue.extractMax();
}
public int size() {
return _queue.size();
}
}