package net.i2p.router.transport.udp; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import net.i2p.data.Hash; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.util.I2PThread; import net.i2p.util.Log; /** * Weighted priority queue implementation for the outbound messages, coupled * with code to fail messages that expire. * * WARNING - UNUSED since 0.6.1.11 * See comments in DummyThrottle.java and mtn history ca. 2006-02-19 * */ class TimedWeightedPriorityMessageQueue implements MessageQueue, OutboundMessageFragments.ActiveThrottle { private RouterContext _context; private Log _log; /** FIFO queue of messages in a particular priority */ private List<OutNetMessage> _queue[]; /** all messages in the indexed queue are at or below the given priority. */ private int _priorityLimits[]; /** weighting for each queue */ private int _weighting[]; /** how many bytes are enqueued */ private long _bytesQueued[]; /** how many messages have been pushed out in this pass */ private int _messagesFlushed[]; /** how many bytes total have been pulled off the given queue */ private long _bytesTransferred[]; /** lock to notify message enqueue/removal (and block for getNext()) */ private final Object _nextLock; /** have we shut down or are we still alive? */ private boolean _alive; /** which queue should we pull out of next */ private int _nextQueue; /** true if a message is enqueued while the getNext() call is in progress */ private volatile boolean _addedSincePassBegan; private Expirer _expirer; private FailedListener _listener; /** set of peers (Hash) whose congestion window is exceeded in the active queue */ private Set<Hash> _chokedPeers; /** * Build up a new queue * * @param priorityLimits ordered breakpoint for the different message * priorities, with the lowest limit first. * @param weighting how much to prefer a given priority grouping. * specifically, this means how many messages in this queue * should be pulled off in a row before moving on to the next. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public TimedWeightedPriorityMessageQueue(RouterContext ctx, int[] priorityLimits, int[] weighting, FailedListener lsnr) { _context = ctx; _log = ctx.logManager().getLog(TimedWeightedPriorityMessageQueue.class); _queue = new List[weighting.length]; _priorityLimits = new int[weighting.length]; _weighting = new int[weighting.length]; _bytesQueued = new long[weighting.length]; _bytesTransferred = new long[weighting.length]; _messagesFlushed = new int[weighting.length]; for (int i = 0; i < weighting.length; i++) { _queue[i] = new ArrayList<OutNetMessage>(8); _weighting[i] = weighting[i]; _priorityLimits[i] = priorityLimits[i]; _messagesFlushed[i] = 0; _bytesQueued[i] = 0; _bytesTransferred[i] = 0; } _alive = true; _nextLock = this; _chokedPeers = Collections.synchronizedSet(new HashSet<Hash>(16)); _listener = lsnr; _context.statManager().createRateStat("udp.timeToEntrance", "Message lifetime until it reaches the UDP system", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.messageQueueSize", "How many messages are on the current class queue at removal", "udp", UDPTransport.RATES); _expirer = new Expirer(); I2PThread t = new I2PThread(_expirer, "UDP outbound expirer"); t.setDaemon(true); t.start(); } public void add(OutNetMessage message) { if (message == null) return; _context.statManager().addRateData("udp.timeToEntrance", message.getLifetime(), message.getLifetime()); int queue = pickQueue(message); long size = message.getMessageSize(); synchronized (_queue[queue]) { _queue[queue].add(message); _bytesQueued[queue] += size; } if (_log.shouldLog(Log.DEBUG)) _log.debug("Added a " + size + " byte message to queue " + queue); synchronized (_nextLock) { _addedSincePassBegan = true; _nextLock.notifyAll(); } message.timestamp("added to queue " + queue); } /** * Grab the next message out of the next queue. This only advances * the _nextQueue var after pushing _weighting[currentQueue] messages * or the queue is empty. This call blocks until either a message * becomes available or the queue is shut down. * * @param blockUntil expiration, or -1 if indefinite * @return message dequeued, or null if the queue was shut down */ public OutNetMessage getNext(long blockUntil) { while (_alive) { _addedSincePassBegan = false; for (int i = 0; i < _queue.length; i++) { int currentQueue = (_nextQueue + i) % _queue.length; synchronized (_queue[currentQueue]) { for (int j = 0; j < _queue[currentQueue].size(); j++) { OutNetMessage msg = _queue[currentQueue].get(j); Hash to = msg.getTarget().getIdentity().getHash(); if (_chokedPeers.contains(to)) continue; // not choked, lets push it to active _queue[currentQueue].remove(j); long size = msg.getMessageSize(); _bytesQueued[currentQueue] -= size; _bytesTransferred[currentQueue] += size; _messagesFlushed[currentQueue]++; if (_messagesFlushed[currentQueue] >= _weighting[currentQueue]) { _messagesFlushed[currentQueue] = 0; _nextQueue = (currentQueue + 1) % _queue.length; } int sz = _queue[currentQueue].size(); _context.statManager().addRateData("udp.messageQueueSize", sz, currentQueue); if (_log.shouldLog(Log.DEBUG)) _log.debug("Pulling a message off queue " + currentQueue + " with " + sz + " remaining"); msg.timestamp("made active with remaining queue size " + sz); return msg; } // nothing waiting, or only choked peers _messagesFlushed[currentQueue] = 0; if (_log.shouldLog(Log.DEBUG)) _log.debug("Nothing available on queue " + currentQueue); } } long remaining = blockUntil - _context.clock().now(); if ( (blockUntil > 0) && (remaining < 0) ) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Nonblocking, or block time has expired"); return null; } try { synchronized (_nextLock) { if (!_addedSincePassBegan && _alive) { // nothing added since we begun iterating through, // so we can safely wait for the full period. otoh, // even if this is true, we might be able to safely // wait, but it doesn't hurt to loop again. if (_log.shouldLog(Log.DEBUG)) _log.debug("Wait for activity (up to " + remaining + "ms)"); if (blockUntil < 0) _nextLock.wait(); else _nextLock.wait(remaining); } } } catch (InterruptedException ie) {} } return null; } public void shutdown() { _alive = false; synchronized (_nextLock) { _nextLock.notifyAll(); } } public void choke(Hash peer) { if (true) return; _chokedPeers.add(peer); synchronized (_nextLock) { _nextLock.notifyAll(); } } public void unchoke(Hash peer) { if (true) return; _chokedPeers.remove(peer); synchronized (_nextLock) { _nextLock.notifyAll(); } } public boolean isChoked(Hash peer) { return _chokedPeers.contains(peer); } private int pickQueue(OutNetMessage message) { int target = message.getPriority(); for (int i = 0; i < _priorityLimits.length; i++) { if (_priorityLimits[i] <= target) { if (i == 0) return 0; else return i - 1; } } return _priorityLimits.length-1; } public interface FailedListener { public void failed(OutNetMessage msg, String reason); } /** * Drop expired messages off the queues */ private class Expirer implements Runnable { public void run() { List<OutNetMessage> removed = new ArrayList<OutNetMessage>(1); while (_alive) { long now = _context.clock().now(); for (int i = 0; i < _queue.length; i++) { synchronized (_queue[i]) { for (int j = 0; j < _queue[i].size(); j++) { OutNetMessage m = _queue[i].get(j); if (m.getExpiration() < now) { _bytesQueued[i] -= m.getMessageSize(); removed.add(m); _queue[i].remove(j); j--; continue; } } } } for (int i = 0; i < removed.size(); i++) { OutNetMessage m = removed.get(i); m.timestamp("expirer killed it"); _listener.failed(m, "expired before getting on the active pool"); } removed.clear(); try { Thread.sleep(1000); } catch (InterruptedException ie) {} } } } }