/** * */ package edu.washington.cs.oneswarm.f2f.network; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import java.util.logging.Logger; import org.gudy.azureus2.core3.global.GlobalManagerStats; import org.gudy.azureus2.core3.util.Average; import org.gudy.azureus2.core3.util.Debug; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.OutgoingMessageQueue; import com.aelitis.azureus.core.peermanager.messaging.Message; import edu.washington.cs.oneswarm.f2f.BigFatLock; import edu.washington.cs.oneswarm.f2f.Friend; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelDataMsg; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelMsg; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelReset; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMessage; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FSearchResp; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FTextSearch; import edu.washington.cs.oneswarm.f2f.network.OverlayTransport.WriteQueueWaiter; import edu.washington.cs.oneswarm.f2f.network.QueueManager.QueueBuckets; import edu.washington.cs.oneswarm.f2f.servicesharing.OSF2FServiceDataMsg; class FriendConnectionQueue implements Comparable<FriendConnectionQueue> { private static BigFatLock lock = OverlayManager.lock; private final static Logger logger = Logger.getLogger(FriendConnectionQueue.class.getName()); public final static int MAX_FRIEND_QUEUE_LENGTH = (OSF2FMessage.MAX_MESSAGE_SIZE + 9); // // private final static int MAX_TRANSPORT_QUEUE_MS = 400; private static final int MAX_INTERNAL_TRANSPORT_QUEUE_LENGTH = MAX_FRIEND_QUEUE_LENGTH; /* * if a search is more than 6s old it is expired */ private final static int MAX_SEARCH_AGE = 6 * 1000; private final static int MAX_SEARCH_QUEUE = 50; public static final boolean QUEUE_LENGTH_DEBUG = System .getProperty("oneswarm.queue.length.debug") != null; private long dataBytesUploaded = 0; private final LinkedList<OSF2FMessage> forwardQueue = new LinkedList<OSF2FMessage>(); private int forwardQueueBytes = 0; private long forwardQueueDelay = 0; /** * This is synchronized by locking on the QueueManager */ // private HashMap<Integer, Long> forwardQueueTimes = new HashMap<Integer, // Long>(); private final Friend friend; private double friendScore = 1; private long lastMessageSentTime; private final NetworkConnection nc; private long protocolBytesUploaded = 0; private final InternalQueueListener queueListener = new InternalQueueListener(); private final QueueManager queueManager; private volatile boolean registeredForForwardSelects = false; private boolean registeredForSearchSelects = false; private volatile boolean registeredForTransportSelects = false; private final LinkedList<QueuedSearch> searchQueue = new LinkedList<QueuedSearch>(); // private final SpeedManager speedManager; private final GlobalManagerStats stats; private final LinkedList<OSF2FMessage> transportQueue = new LinkedList<OSF2FMessage>(); private volatile int transportQueueBytes = 0; private long transportQueueDelay = 0; private final ConcurrentLinkedQueue<WriteQueueWaiter> transportWaiters = new ConcurrentLinkedQueue<WriteQueueWaiter>(); private final Average uploadAverage = Average.getInstance(1000, 10); private PacketListener packetListener; /* Used in ClientServiceConnection and ServerServiceConnection unit tests */ private OSF2FMessage lastMsgQueued; public FriendConnectionQueue(QueueManager queueManager, FriendConnection fc) { // this.friendConnection = fc; this.nc = fc.getNetworkConnection(); this.friend = fc.getRemoteFriend(); this.queueManager = queueManager; // this.speedManager = new SpeedManager(queueManager, false); this.stats = fc.getStats(); getFriendScore(true); nc.getOutgoingMessageQueue().registerQueueListener(queueListener); logger.fine(getDescription() + "connection queue created"); } /** * closes the queue, also returns any messages that are still in the queue * for the queuemanager to free in it's next checking run * * @return */ List<OSF2FMessage> close() { nc.getOutgoingMessageQueue().cancelQueueListener(queueListener); lock.lock(); try { List<OSF2FMessage> queuedMessages = new LinkedList<OSF2FMessage>(); OSF2FMessage m; while ((m = transportQueue.poll()) != null) { queuedMessages.add(m); } while ((m = forwardQueue.poll()) != null) { queuedMessages.add(m); } QueuedSearch qs; while ((qs = searchQueue.poll()) != null) { m = qs.getSearchMessage(); if (m != null) { queuedMessages.add(m); } } return queuedMessages; } finally { lock.unlock(); } } @Override public int compareTo(FriendConnectionQueue o) { if (friendScore > o.getFriendScore(false)) { return 1; } else if (friendScore == o.getFriendScore(false)) { return 0; } else { return -1; } } void doListenerNotifications() { if (QueueManager.QUEUE_LOCK_DEBUG) { if (lock.isHeldByCurrentThread()) { Debug.out("holding queue manager lock!!!"); } } nc.getOutgoingMessageQueue().doListenerNotifications(); } public int getCurrentUploadSpeedInBps() { return (int) uploadAverage.getAverage(); } // private final FriendConnection friendConnection; public long getDataBytesUploaded() { return dataBytesUploaded; } public String getDebug() { StringBuilder b = new StringBuilder(); lock.lock(); try { b.append(" network connection=" + getTotalOutgoingQueueLengthBytes()); b.append(" forward: reg=" + registeredForForwardSelects + " forwardQueue_num=" + forwardQueue.size() + " forwardQueue_bytes=" + forwardQueueBytes + "\n"); b.append(" transport: reg=" + registeredForTransportSelects + " can_write=" + isReadyForTransportWrite(null) + " waiters=" + transportWaiters.size() + " num=" + transportQueue.size() + " bytes=" + transportQueueBytes + "\n"); b.append(" search: reg=" + registeredForSearchSelects + " searchQueueLen=" + searchQueue.size() + "\n"); } finally { lock.unlock(); } return b.toString(); } private String getDescription() { return friend.getNick() + ": "; } public int getForwardQueueBytes() { return forwardQueueBytes; } public long getForwardQueueDelay() { return forwardQueueDelay; } /* * return the friend score of the friend associated with this queue * * @param update set to true if the score should be recomputed, false if the * cached score should be returned. Only use cached values unless the * QueueWeight array has been sorted to reflect the new values */ public double getFriendScore(boolean update) { if (update) { friendScore = friend.getFriendScore(); // the friend score used is capped between 0.2 and 5 // we might want to use a different function here to // better reward contribution friendScore = Math.max(friendScore, 0.2); friendScore = Math.min(friendScore, 5); } return friendScore; } /** * Used for ClientServiceConnection and ServerServiceConnection unit tests * @return last message passed into queuePacketForceQueue */ public OSF2FMessage getLastMessageQueued() { return lastMsgQueued; } public long getLastMessageSentTime() { return System.currentTimeMillis() - lastMessageSentTime; } public long getProtocolBytesUploaded() { return protocolBytesUploaded; } public int getTotalOutgoingQueueLengthBytes() { return nc.getOutgoingMessageQueue().getTotalSize(); } boolean isReadyForTransportWrite(WriteQueueWaiter waiter) { // boolean writeable = speedManager.canQueuePacket(transportQueueBytes, // MAX_TRANSPORT_QUEUE_MS); boolean writeable = transportQueueBytes < MAX_INTERNAL_TRANSPORT_QUEUE_LENGTH; if (writeable == false && waiter != null) { transportWaiters.add(waiter); } return writeable; } public boolean isRegisteredForForwardSelects() { return registeredForForwardSelects; } public boolean isRegisteredForSearchSelects() { return registeredForSearchSelects; } public boolean isRegisteredForTransportSelects() { return registeredForTransportSelects; } /* * this function must be called from a thread that has the lock */ private int pruneExpiredSearches() { if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } int removedNum = 0; QueuedSearch s; while ((s = searchQueue.peek()) != null && s.isExpired()) { removedNum++; searchQueue.remove(); } return removedNum; } private boolean queueFull() { return this.getTotalOutgoingQueueLengthBytes() > MAX_FRIEND_QUEUE_LENGTH; } public void queuePacketForceQueue(QueueBuckets bucket, OSF2FMessage msg) { /* * this code needs the queue manager lock */ boolean triggerPacketSending = false; lock.lock(); try { if (bucket == QueueBuckets.TRANSPORT) { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "queueing transport: " + msg.getDescription()); } lastMsgQueued = msg; transportQueue.add(msg); transportQueueBytes += (msg).getMessageSize(); if (QueueManager.QUEUE_DEBUG_LOGGING) { // the size operation is expensive, don't run unless // debugging if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "transport queue size: " + transportQueue.size() + " bytes=" + transportQueueBytes + " registered=" + registeredForTransportSelects); } } if (!registeredForTransportSelects) { triggerPacketSending = true; registerForTransportSelects(); } } else if (bucket == QueueBuckets.FORWARD || msg instanceof OSF2FSearchResp) { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "queueing forward: " + msg.getDescription()); } forwardQueue.add(msg); forwardQueueBytes += getMessageLen(msg); // forwardQueueTimes.put(msg.hashCode(), // System.currentTimeMillis()); if (!registeredForForwardSelects) { triggerPacketSending = true; registerForForwardSelects(); } } else if (msg instanceof OSF2FSearch) { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "queueing search packet: " + msg.getDescription() + ", queue len=" + searchQueue.size()); } pruneExpiredSearches(); if (searchQueue.size() >= MAX_SEARCH_QUEUE) { final QueuedSearch drop = searchQueue.removeFirst(); if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "dropping search packet from head: " + drop.getSearchMessage().getDescription() + ", queue is too long"); } drop.getSearchMessage().destroy(); } searchQueue.add(new QueuedSearch((OSF2FSearch) msg)); if (!registeredForSearchSelects) { triggerPacketSending = true; registerForSearchSelects(); } } else { Debug.out(getDescription() + "unhandled message: " + msg.getDescription()); } } finally { lock.unlock(); } /* * trigger packet sending outside the lock */ if (triggerPacketSending) { queueManager.triggerPacketSending(); } } public void queuePacket(QueueManager.QueueBuckets bucket, OSF2FMessage msg, boolean skipQueue) { if ((!requiresQueueHandling(msg)) || skipQueue) { // control traffic will just skip the queue, no reason to bother // the queue manager if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "sending packet straight to network: " + msg.getDescription()); } nc.getOutgoingMessageQueue().addMessage(msg, false); } else { queuePacketForceQueue(bucket, msg); } } private void registerForForwardSelects() { if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } if (QueueManager.QUEUE_DEBUG_LOGGING) { if (registeredForForwardSelects) { Debug.out("tried to register for forward selects, but we are already registered!!!"); } } registeredForForwardSelects = true; queueManager.registerForForwardSelects(FriendConnectionQueue.this); } private void registerForSearchSelects() { if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } if (QueueManager.QUEUE_DEBUG_LOGGING) { if (registeredForSearchSelects) { Debug.out("tried to register for search selects, but we are already registered!!!"); } } registeredForSearchSelects = true; queueManager.registerForSearchSelects(FriendConnectionQueue.this); } private void registerForTransportSelects() { if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } if (QueueManager.QUEUE_DEBUG_LOGGING) { if (registeredForTransportSelects) { Debug.out("tried to register for search selects, but we are already registered!!!"); } } registeredForTransportSelects = true; queueManager.registerForTransportSelects(FriendConnectionQueue.this); } /** * this function should only be called from the queue manager, it will * return true if more packets can be queued, false otherwise */ boolean sendQueuedForwardPacket() { registeredForForwardSelects = false; boolean packetSent = false; if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } boolean stayRegistered = false; /* * first, check if we can queue more */ if (queueFull()) { stayRegistered = false; } /* * second, check if we have anything * * use peek instead of size, size is a O(n) operation on * concurrentlinkedqueues */ else if (forwardQueue.peek() == null) { Debug.out(getDescription() + "sendQueuedForwardPacket() called, but noting to forward!"); stayRegistered = false; } /* * we have packets to send, send it!!! */ else { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "sending packet from forward queue, len=" + forwardQueue.size() + "(bytes=" + forwardQueueBytes + ")"); } OSF2FMessage forwardedMessage = forwardQueue.remove(); packetListenerNotify(forwardedMessage); nc.getOutgoingMessageQueue().addMessage(forwardedMessage, true); packetSent = true; int numBytes = getMessageLen(forwardedMessage); stats.protocolBytesSent(numBytes, nc.isLANLocal()); forwardQueueBytes -= numBytes; if (forwardQueueBytes < 0) { logger.warning(getDescription() + "accounting error: forwardQueueBytes < 0 in friend connection queue: " + friend); } /* * hash collitions are rare, but they can happen.... */ // Long queuedTime = // forwardQueueTimes.remove(forwardedMessage.hashCode()); // if (queuedTime != null) { // forwardQueueMs = System.currentTimeMillis() - queuedTime; // } else { // forwardQueueMs = 0; // } if (forwardQueue.peek() != null) { stayRegistered = true; } else { stayRegistered = false; } } /* * update the registered flag */ if (stayRegistered) { /* * register for more selects, but we don't need to trigger packet * sending, the queue manager will just continue in the while loop */ registerForForwardSelects(); } return packetSent; } /** * Notifies the packet listener on channel messages. * * @param message * @return false if the packet has been handled. */ protected boolean packetListenerNotify(OSF2FMessage message) { if (packetListener != null && (message instanceof OSF2FServiceDataMsg)) { OSF2FChannelMsg channelMessage = (OSF2FServiceDataMsg) message; logger.info("NOTIFY, bytes=" + channelMessage.getByteInChannel()); return packetListener.packetReadyForAzureusQueue(channelMessage); } return true; } /** * this function should only be called from the queue manager, it will * return true if more packets are available, false otherwise */ boolean sendQueuedSearchPacket() { logger.finest(getDescription() + "sendQueuedSearchPacket()"); registeredForSearchSelects = false; if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } boolean packetSent = false; boolean stayRegistered = false; // remove anything that has expired from the queue int pruned = pruneExpiredSearches(); /* * first, check if we can queue more */ if (queueFull()) { stayRegistered = false; } /* * then, check if we actually have anything to send */ else if (searchQueue.peek() == null) { if (pruned == 0) { Debug.out(getDescription() + "sendQueuedSearchPacket() called, but noting to forward!"); } stayRegistered = false; } else { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "sending packet from search queue"); } Message search = searchQueue.remove().getSearchMessage(); nc.getOutgoingMessageQueue().addMessage(search, true); packetSent = true; if (searchQueue.peek() != null) { stayRegistered = true; } else { stayRegistered = false; } } if (stayRegistered) { /* * register for more selects, but we don't need to trigger packet * sending, the queue manager will just continue in the while loop */ registerForSearchSelects(); } if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "sendQueuedSearchPacket(), sent=" + packetSent + " registered=" + registeredForSearchSelects + "," + stayRegistered); } return packetSent; } /** * this function should only be called from the queue manager, it will * return true if more packets are available, false otherwise * * make sure to have the queue manager lock when calling this funtion */ boolean sendQueuedTransportPacket() { boolean packetSent = false; registeredForTransportSelects = false; if (QueueManager.QUEUE_LOCK_DEBUG) { if (!lock.isHeldByCurrentThread()) { Debug.out("not holding queue manager lock!!!"); } } boolean stayRegistered = false; /* * first, check if we can queue more */ if (queueFull()) { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "not queueing transport packet, transport queue already full"); } stayRegistered = false; } /* * second, we might have waiters ready to write. Let them fill up the * queue */ else { int notified = 0; while (transportWaiters.peek() != null && isReadyForTransportWrite(null)) { transportWaiters.remove().readyForWrite(); notified++; } if (QueueManager.QUEUE_DEBUG_LOGGING && logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "notifying waiters, num=" + notified + " left=" + transportWaiters.size()); } /* * check if we actually have anything to send */ if (transportQueue.peek() == null) { stayRegistered = false; Debug.out(getDescription() + "sendQueuedTransportPacket() called, but noting to send!"); } else { if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "sending packet from transport queue"); } OSF2FMessage msg = transportQueue.remove(); packetListenerNotify(msg); nc.getOutgoingMessageQueue().addMessage(msg, true); packetSent = true; transportQueueBytes -= msg.getMessageSize(); } if (QueueManager.QUEUE_DEBUG_LOGGING && logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + "transport queue size: " + transportQueue.size()); } // if the queue size if (transportQueue.peek() != null) { stayRegistered = true; } else { stayRegistered = false; } } if (stayRegistered) { /* * register for more selects, but we don't need to trigger packet * sending, the queue manager will just continue in the while loop */ registerForTransportSelects(); } return packetSent; } @Override public String toString() { NumberFormat f = new DecimalFormat("#,###,###"); final String nick = friend.getNick(); return "QueueManager: " + nick.substring(0, Math.min(8, nick.length())) + "\tnetBytes=" + nc.getOutgoingMessageQueue().getTotalSize() + "\tfwDelay=" + getForwardQueueDelay() + "\tfwBytes=" + f.format(getForwardQueueBytes()) + "\ttrBytes=" + f.format(transportQueueBytes) + "\ttrDelay" + transportQueueDelay + "\tsLen=" + searchQueue.size(); } public static int getMessageLen(Message message) { int len = 4 + 1;// OSF2FMessageFatory:166 // final DirectByteBuffer[] data = message.getData(); // for (DirectByteBuffer d : data) { // len += d.remaining(DirectByteBuffer.SS_MSG); // } if (message instanceof OSF2FMessage) { len += ((OSF2FMessage) message).getMessageSize(); } return len; } public static boolean requiresQueueHandling(Message msg) { if (msg instanceof OSF2FChannelDataMsg) { return true; } if (msg instanceof OSF2FHashSearch) { return true; } /* * Text searches are really two things: 1). a FILE_LIST_TYPE_COMPLETE is * the intial file list request that is sent during handshake and * contains the complete file list. These shouldn't be rate-limited so * that connections / handshakes are fast even when the queue is full * * 2). Real text searches -- of which there may be many */ if (msg instanceof OSF2FTextSearch) { if (((OSF2FTextSearch) msg).getRequestType() == OSF2FTextSearch.FILE_LIST_TYPE_COMPLETE) { return false; } else { return true; } } if (msg instanceof OSF2FChannelReset) { return true; } /* * search responses are queued as well to make the order they appear in * reflect the congestion on the link, and to make sure that search * cancels will cancel searches over slow links */ if (msg instanceof OSF2FSearchResp) { return true; } return false; } class InternalQueueListener implements OutgoingMessageQueue.MessageQueueListener { private static final long BYTES_MESSAGES_COMPARISON_INTERVAL = 5 * 1000; /** * -------------------------------------------------------------------- * ----------------------------- This debugging code is only active when * logging at the FINER or finer granularity and tracks the status of * individual Messages */ private static final long MAX_MESSAGE_LOG_HISTORY = 10 * 1000; /** * This should be the queue length, is it drifting? We compare this to * the authoritative Azureus queue. */ private long accountingQueueLength = 0; private long mAddedMessagesBytes = 0; private long mLastByteMessageCompare = 0; private final ConcurrentLinkedQueue<MessageDigest> mMessages = new ConcurrentLinkedQueue<MessageDigest>(); /** * We use these to check the drift between dataBytesUploaded and the sum * of the sent message sizes */ private long mSentMessagesBytes = 0; private long packetNum = 0; /** * -------------------------------------------------------------------- * ----------------------------- */ @Override public void dataBytesSent(int byte_count) { dataBytesUploaded += byte_count; friend.updateUploaded(byte_count); stats.f2fBytesSent(byte_count); uploadAverage.addValue(byte_count); } private void examineMessageHistory() { while (mMessages.peek().timestamp + MAX_MESSAGE_LOG_HISTORY < System .currentTimeMillis()) { logger.finer(getDescription() + "Expiring: " + mMessages.remove() + " from FriendConnectionQueue of " + friend.getNick()); } } @Override public void flush() { } @Override public boolean messageAdded(Message message) { int len = getMessageLen(message); queueManager.messageQueued(len); mAddedMessagesBytes += len; accountingQueueLength += len; if (logger.isLoggable(Level.FINER) && QUEUE_LENGTH_DEBUG) { mMessages.add(new MessageDigest(message)); examineMessageHistory(); if (mLastByteMessageCompare + BYTES_MESSAGES_COMPARISON_INTERVAL < System .currentTimeMillis()) { perFriendQueueLog(); mLastByteMessageCompare = System.currentTimeMillis(); } } return (true); } @Override public void messageQueued(Message message) { } @Override public void messageRemoved(Message message) { /* * check if we need to reregister */ packetSentCheckRegistrations(); int len = getMessageLen(message); queueManager.messageSent(len); if (logger.isLoggable(Level.FINER)) { mMessages.remove(new MessageDigest(message)); } accountingQueueLength -= len; } @Override public void messageSent(Message message) { /* * check if we need to reregister */ packetSentCheckRegistrations(); int len = getMessageLen(message); // speedManager.dataUploaded(len); queueManager.messageSent(len); packetNum++; if (logger.isLoggable(Level.FINEST) && packetNum % 100 == 0) { logger.finest(getDescription() + " sent: " + message.getDescription() + " forward queue: " + forwardQueueBytes + " \t::" + friend); } if (logger.isLoggable(Level.FINER)) { mMessages.remove(new MessageDigest(message)); } mSentMessagesBytes += len; accountingQueueLength -= len; lastMessageSentTime = System.currentTimeMillis(); if (message instanceof OSF2FChannelDataMsg) { OSF2FChannelDataMsg msg = (OSF2FChannelDataMsg) message; long delay = System.currentTimeMillis() - ((OSF2FChannelDataMsg) message).getCreatedTime(); if (msg.isForward()) { forwardQueueDelay = delay; } else { transportQueueDelay = delay; } } } /* * When packets are removed from the queue, check if we need to * reregister with the queue manager, we might have become deregistered * if we had a long queue and therefore were unable to send a packet * when asked */ private void packetSentCheckRegistrations() { lock.lock(); try { /* * if the transport queue contains elements, and we are not * registered, register */ if (transportQueue.peek() != null && !registeredForTransportSelects) { registerForTransportSelects(); } /* * same for forwards */ if (forwardQueue.peek() != null && !registeredForForwardSelects) { registerForForwardSelects(); } /* * and for searches */ if (searchQueue.peek() != null && !registeredForSearchSelects) { registerForSearchSelects(); } } finally { lock.unlock(); } } private void perFriendQueueLog() { logger.finer(getDescription() + "sent_message_bytes=" + mSentMessagesBytes + " added message bytes=" + mAddedMessagesBytes + " protocol_bytes=" + protocolBytesUploaded + " data_bytes=" + dataBytesUploaded + " accouting_queue_len=" + accountingQueueLength + " az_queue_len=" + nc.getOutgoingMessageQueue().getTotalSize() + " forwardQueueBytes=" + forwardQueueBytes + " transportQueueBytes=" + transportQueueBytes + " searchQueueLen=" + searchQueue.size()); } @Override public void protocolBytesSent(int byte_count) { protocolBytesUploaded += byte_count; friend.updateUploaded(byte_count); stats.f2fBytesSent(byte_count); uploadAverage.addValue(byte_count); } class MessageDigest { Message message = null; long timestamp = 0; public MessageDigest(Message m) { message = m; timestamp = System.currentTimeMillis(); } @Override public boolean equals(Object rhs) { if (rhs instanceof MessageDigest) { return ((MessageDigest) rhs).message == message; } return false; } @Override public String toString() { return message.toString(); } } } private static class QueuedSearch { final OSF2FSearch search; final long timeStamp; public QueuedSearch(OSF2FSearch search) { super(); this.search = search; this.timeStamp = System.currentTimeMillis(); } public OSF2FSearch getSearchMessage() { return search; } public boolean isExpired() { long age = System.currentTimeMillis() - timeStamp; return age > MAX_SEARCH_AGE; } } public void clearForwardChannel(int channelId) { if (QueueManager.QUEUE_LOCK_DEBUG) { if (lock.isHeldByCurrentThread()) { Debug.out("holding queue manager lock!!!"); } } lock.lock(); try { int count = 0; for (Iterator<OSF2FMessage> iterator = forwardQueue.iterator(); iterator.hasNext();) { OSF2FMessage osf2fMessage = iterator.next(); if (osf2fMessage instanceof OSF2FChannelMsg) { OSF2FChannelMsg msg = (OSF2FChannelMsg) osf2fMessage; if (msg.getChannelId() == channelId) { iterator.remove(); count++; if (logger.isLoggable(Level.FINEST)) { logger.finest("removing channel message from friend queue: " + msg.getDescription() + ": (channel closed)"); } forwardQueueBytes -= getMessageLen(msg); msg.destroy(); } } } logger.finer("channel closed, removed " + count + " messages"); } finally { lock.unlock(); } doListenerNotifications(); } /** * Returns the contribution of this individual {@code FriendConnectionQueue} * to the overall global queue (maintained by {@code QueueManager}. * * This is used to enforce per-friend limits on the share of the global * queue. */ public long getTotalOutgoingBytesContributionToGlobalQueue() { return queueListener.accountingQueueLength; } public void setPacketListener(PacketListener listener) { this.packetListener = listener; } }