Package freenet.node

Source Code of freenet.node.PeerMessageQueue$PrioQueue$Items

package freenet.node;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;

import freenet.io.comm.DMT;
import freenet.support.DoublyLinkedList;
import freenet.support.DoublyLinkedListImpl;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.MutableBoolean;

/**
* Queue of messages to send to a node. Ordered first by priority then by time.
* Will soon be round-robin between different transfers/UIDs/clients too.
* @author Matthew Toseland <toad@amphibian.dyndns.org> (0xE43DA450)
*/
public class PeerMessageQueue {

  private static volatile boolean logMINOR;
  private static volatile boolean logDEBUG;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
        logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
      }
    });
  }

  private final PrioQueue[] queuesByPriority;
 
  private boolean mustSendLoadRT;
  private boolean mustSendLoadBulk;
 
  private class PrioQueue {
   
    // FIXME refactor into PrioQueue and RoundRobinByUIDPrioQueue
    PrioQueue(long timeout, boolean timeoutSinceLastSend) {
      this.timeout = timeout;
      this.roundRobinBetweenUIDs = timeoutSinceLastSend;
    }
   
    /** The timeout, period after which messages become urgent. */
    final long timeout;
    /** If true, do round-robin between UID's, and count the timeout relative
     * to the last send. Block transfers need this - both realtime and bulk. */
    final boolean roundRobinBetweenUIDs;
   
    private class Items extends DoublyLinkedListImpl.Item<Items> {
      /** List of messages to send. Stuff to send first is at the beginning. */
      final LinkedList<MessageItem> items;
      final long id;
      long timeLastSent;
      Items(long id, long initialTimeLastSent) {
        items = new LinkedList<MessageItem>();
        this.id = id;
        timeLastSent = initialTimeLastSent;
      }
      public void addLast(MessageItem item) {
        items.addLast(item);
      }
      public void addFirst(MessageItem item) {
        items.addFirst(item);
      }
      public boolean remove(MessageItem item) {
        return items.remove(item);
      }
      @Override
      public String toString() {
        return super.toString()+":"+id+":"+items.size()+":"+timeLastSent;
      }
    }
   
    /** Maximum inter-packet time is 2 minutes for a block transfer (when we have bulk
     * flag this will be no higher, and it might be reduced to 30 seconds). Requests
     * can wait for 2 minutes now, maybe 10 minutes in future, but round-robin is
     * intended for frequent messages - it doesn't matter in that case. So 3 minutes
     * is plenty. */
    static final long FORGET_AFTER = 3*60*1000;

    /** Using DoublyLinkedListImpl so that we can move stuff around without the
     * iterator failing, and also delete efficiently. Ordered by timeLastSent,
     * NOT by timeout. Items we have not yet sent are at the beginning with
     * timeLastSent = -1. */
    DoublyLinkedListImpl<Items> nonEmptyItemsWithID;
    /** Items which have been sent within the last 10 minutes, so we need to track
     * them for good round-robin, but which we don't have anything queued on right now. */
    DoublyLinkedListImpl<Items> emptyItemsWithID;
    Map<Long, Items> itemsByID;
    /** Non-urgent messages. Same order as in Items, so stuff to send first is at
     * the beginning. */
    LinkedList<MessageItem> itemsNonUrgent;
    // Construct structures lazily, we're protected by the overall synchronized.

    /** Add a new message. For a normal priority level, we just add it to the end of the list.
     * It will be sent after the messages that are already queued, and its deadline is effectively
     * the time it was submitted plus the timeout. For a priority level using round robin between
     * peers, it is the same unless we have recently sent a message with the same UID. If we have,
     * the timeout is relative to the last send. */
    public void addLast(MessageItem item) {
      // Clear the deadline for the item.
      item.clearDeadline();
      if(logMINOR) checkOrder();
      if(roundRobinBetweenUIDs) {
        long id = item.getID();
        if(itemsByID != null) {
          Items it = itemsByID.get(id);
          if(it != null && it.timeLastSent > 0 && it.timeLastSent + timeout <= System.currentTimeMillis()) {
            it.addLast(item);
            if(it.getParent() == emptyItemsWithID)
              moveFromEmptyToNonEmptyBackward(it);
            else
              assert(it.getParent() == nonEmptyItemsWithID);
            if(logMINOR) checkOrder();
            return;
          }
        }
      }
      addToNonUrgent(item);
    }
   
    private void addToNonUrgent(MessageItem item) {
      if(itemsNonUrgent == null)
        itemsNonUrgent = new LinkedList<MessageItem>();
      ListIterator<MessageItem> it = itemsNonUrgent.listIterator(itemsNonUrgent.size());
      // MessageItem's can be created out of order, so the timestamps may not be consistent.
      // CONCURRENCY: This is not a problem in addNonUrgentMessages() because it is always called from one thread.
      while(true) {
        if(!it.hasPrevious()) {
          it.add(item);
          if(logMINOR) checkOrder();
          return;
        }
        MessageItem prev = it.previous();
        if(item.submitted >= prev.submitted) {
          it.next();
          it.add(item);
          if(logMINOR) checkOrder();
          return;
        }
      }
    }

    private void moveToUrgent(long now) {
      if(logMINOR) checkOrder();
      if(itemsNonUrgent == null) return;
      ListIterator<MessageItem> it = itemsNonUrgent.listIterator();
      int moved = 0;
      while(it.hasNext()) {
        MessageItem item = it.next();
        Items list = null;
        long id = item.getID();
        if(itemsByID != null)
          list = itemsByID.get(id);
        boolean moveIt = false;
        if(list != null && roundRobinBetweenUIDs) {
          if(list.timeLastSent + timeout <= now)
            moveIt = true;
        }
        if(item.submitted + timeout <= now) {
          moveIt = true;
        }
        if(moveIt) {
          if(logMINOR) Logger.minor(this, "Moving message to urgent list: "+item);
          if(logMINOR) checkOrder();
          // Move to urgent list
          if(itemsByID == null) {
            itemsByID = new HashMap<Long, Items>();
            if(nonEmptyItemsWithID == null)
              nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
            list = new Items(id, item.submitted);
            addToNonEmptyForward(list);
            itemsByID.put(id, list);
            if(logMINOR) checkOrder();
          } else {
            if(list == null) {
              list = new Items(id, item.submitted);
              if(nonEmptyItemsWithID == null)
                nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
              addToNonEmptyForward(list);
              itemsByID.put(id, list);
              if(logMINOR) checkOrder();
            } else {
              if(list.items.isEmpty()) {
                if(list.getParent() == nonEmptyItemsWithID) {
                  Logger.error(this, "Was empty but was in nonEmptyItemsWithID: "+list);
                } else {
                  assert(list.getParent() == emptyItemsWithID);
                  // It already exists, so it has a valid time.
                  // Which is probably in the past, so use Forward.
                  // Must add it to the list before moving to non-empty because of assertion.
                  moveFromEmptyToNonEmptyForward(list);
                }
              } else {
                assert(list.getParent() == nonEmptyItemsWithID);
              }
              if(logMINOR) checkOrder();
            }
          }
          list.addLast(item);
          it.remove();
          moved++;
          if(logMINOR) checkOrder();
        } else if(!roundRobinBetweenUIDs)
          break;
      }
      if(logDEBUG && moved > 0)
        Logger.debug(this, "Moved "+moved+" items to urgent round-robin");
      if(logMINOR) checkOrder();
    }

    private void moveFromEmptyToNonEmptyForward(Items list) {
      // Presumably is in emptyItemsWithID
      assert(list.items.isEmpty());
      if(logMINOR) {
        if(list.getParent() == nonEmptyItemsWithID) {
          Logger.error(this, "Already in non-empty yet empty?!");
          return;
        }
      }
      if(emptyItemsWithID != null)
        emptyItemsWithID.remove(list);
      addToNonEmptyForward(list);
    }
   
    private void addToNonEmptyForward(Items list) {
      if(nonEmptyItemsWithID == null)
        nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
      Enumeration<Items> it = nonEmptyItemsWithID.elements();
      while(it.hasMoreElements()) {
        Items compare = it.nextElement();
        if(compare.timeLastSent >= list.timeLastSent) {
          nonEmptyItemsWithID.insertPrev(compare, list);
          return;
        }
      }
      nonEmptyItemsWithID.push(list);
    }

    private void moveFromEmptyToNonEmptyBackward(Items list) {
      // Presumably is in emptyItemsWithID
      emptyItemsWithID.remove(list);
      addToNonEmptyBackward(list);
    }
   
    private void addToNonEmptyBackward(Items list) {
      if(nonEmptyItemsWithID == null)
        nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
      Enumeration<Items> it = nonEmptyItemsWithID.reverseElements();
      while(it.hasMoreElements()) {
        Items compare = it.nextElement();
        if(compare.timeLastSent <= list.timeLastSent) {
          nonEmptyItemsWithID.insertNext(compare, list);
          return;
        }
      }
      nonEmptyItemsWithID.unshift(list);
    }

    private void addToEmptyBackward(Items list) {
      if(emptyItemsWithID == null)
        emptyItemsWithID = new DoublyLinkedListImpl<Items>();
      Enumeration<Items> it = emptyItemsWithID.reverseElements();
      while(it.hasMoreElements()) {
        Items compare = it.nextElement();
        if(compare.timeLastSent <= list.timeLastSent) {
          emptyItemsWithID.insertNext(compare, list);
          return;
        }
      }
      emptyItemsWithID.unshift(list);
    }

    /** Add a new message to the beginning i.e. send it as soon as possible (e.g. if
     * we tried to send it and failed); it is assumed to already be urgent. */
    public void addFirst(MessageItem item) {
      // Keep the old deadline for the item.
      if(!roundRobinBetweenUIDs) {
        addToNonUrgent(item);
        return;
      }
      if(logMINOR) checkOrder();
      long id = item.getID();
      Items list;
      if(itemsByID == null) {
        itemsByID = new HashMap<Long, Items>();
        if(nonEmptyItemsWithID == null)
          nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
        list = new Items(id, -1);
        addToNonEmptyForward(list);
        itemsByID.put(id, list);
      } else {
        list = itemsByID.get(id);
        if(list == null) {
          list = new Items(id, -1);
          if(nonEmptyItemsWithID == null)
            nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
          nonEmptyItemsWithID.unshift(list);
          itemsByID.put(id, list);
        } else {
          if(list.items.isEmpty()) {
            assert(list.getParent() == emptyItemsWithID);
            // It already exists, so it has a valid time.
            // Which is probably in the past, so use Forward.
            moveFromEmptyToNonEmptyForward(list);
          } else
            assert(list.getParent() == nonEmptyItemsWithID);
        }
      }
      list.addFirst(item);
      if(logMINOR) checkOrder();
    }

    public int size() {
      int size = 0;
      if(nonEmptyItemsWithID != null)
        for(Items items : nonEmptyItemsWithID)
          size += items.items.size();
      if(itemsNonUrgent != null)
        size += itemsNonUrgent.size();
      return size;
    }

    public int addTo(MessageItem[] output, int ptr) {
      if(nonEmptyItemsWithID != null)
        for(Items list : nonEmptyItemsWithID)
          for(MessageItem item : list.items)
            output[ptr++] = item;
      if(itemsNonUrgent != null)
        for(MessageItem item : itemsNonUrgent)
          output[ptr++] = item;
      return ptr;
    }
   
    /** Check that nonEmptyItemsWithID is ordered correctly.
     * LOCKING: Caller must synchronize on PeerMessageQueue.this. */
    private void checkOrder() {
      if(nonEmptyItemsWithID != null) {
        long prev = -1;
        Items prevItems = null;
        for(Items items : nonEmptyItemsWithID) {
          long thisTime = items.timeLastSent;
          if(thisTime < prev)
            Logger.error(this, "Inconsistent order in non empty items with ID: prev timeout was "+prev+" for "+prevItems+" but this timeout is "+thisTime+" for "+items, new Exception("error"));
          prev = thisTime;
          prevItems = items;
        }
      }
      if(itemsNonUrgent != null) {
        long prev = -1;
        MessageItem prevItem = null;
        for(MessageItem item : itemsNonUrgent) {
          if(item.submitted < prev)
            Logger.error(this, "Inconsistent order in itemsNonUrgent: prev submitted at "+prev+" but this at "+item.submitted+" prev is "+prevItem+" this is "+item);
          prev = item.submitted;
          prevItem = item;
        }
      }
    }

    /** Note that this does NOT consider the length of the queue, which can trigger a
     * send. This is intentional, and is relied upon by the bulk-or-realtime logic in
     * addMessages().
     * @param t The initial urgent time. What we return must be less than or
     * equal to this. Convenient for chaining.
     * @param stopIfBeforeTime If the next urgent time is <= to this time,
     * return immediately.
     */
    public long getNextUrgentTime(long t, long stopIfBeforeTime) {
      if(!roundRobinBetweenUIDs) {
        if(itemsNonUrgent != null && !itemsNonUrgent.isEmpty()) {
          t = Math.min(t, itemsNonUrgent.getFirst().submitted + timeout);
          if(t <= stopIfBeforeTime) return t;
        }
        assert(nonEmptyItemsWithID == null);
        assert(itemsByID == null);
      } else {
        if(nonEmptyItemsWithID != null) {
          for(Items items : nonEmptyItemsWithID) {
            if(items.items.size() == 0) continue;
            if(items.timeLastSent > 0) {
              t = Math.min(t, items.timeLastSent + timeout);
              if(t <= stopIfBeforeTime) return t;
            } else {
              // It is possible that something requeued isn't urgent, so check anyway.
              t = Math.min(t, items.items.getFirst().submitted + timeout);
              if(t <= stopIfBeforeTime) return t;
            }
          }
        }
        if(itemsNonUrgent != null && !itemsNonUrgent.isEmpty()) {
          for(MessageItem item : itemsNonUrgent) {
            long uid = item.getID();
            Items items = itemsByID == null ? null : itemsByID.get(uid);
            if(items != null && items.timeLastSent > 0) {
              t = Math.min(t, items.timeLastSent + timeout);
              if(t <= stopIfBeforeTime) return t;
            } else {
              t = Math.min(t, item.submitted + timeout);
              if(t <= stopIfBeforeTime) return t;
              if(itemsByID == null) break; // Only the first one matters, since none have been sent.
            }
          }
        }
      }
      return t;
    }

    /**
     * Add the size of messages in this queue to <code>length</code> until
     * length is larger than <code>maxSize</code>, or all messages have
     * been added.
     * @param length the starting length
     * @param maxSize the size at which to stop
     * @return the resulting length after adding messages
     */
    public int addSize(int length, int maxSize) {
      if(itemsNonUrgent != null) {
        for(MessageItem item : itemsNonUrgent) {
          int thisLen = item.getLength();
          length += thisLen;
          if(length > maxSize) return length;
        }
      }
      if(nonEmptyItemsWithID != null) {
        for(Items list : nonEmptyItemsWithID) {
          for(MessageItem item : list.items) {
            int thisLen = item.getLength();
            length += thisLen;
            if(length > maxSize) return length;
          }
        }
      }
      return length;
    }
   
    private MessageItem addNonUrgentMessages(long now, MutableBoolean addPeerLoadStatsRT, MutableBoolean addPeerLoadStatsBulk) {
      if(logMINOR) checkOrder();
      if(itemsNonUrgent == null) return null;
      MessageItem ret;
      for(ListIterator<MessageItem> items = itemsNonUrgent.listIterator();items.hasNext();) {
        MessageItem item = items.next();
        items.remove();
        item.setDeadline(item.submitted + timeout);
        ret = item;
        if(itemsByID != null) {
          long id = item.getID();
          Items tracker = itemsByID.get(id);
          if(tracker != null) {
            tracker.timeLastSent = now;
            DoublyLinkedList<? super Items> parent = tracker.getParent();
            // Demote the corresponding tracker to maintain round-robin.
            if(tracker.items.isEmpty()) {
              if(logDEBUG) Logger.debug(this, "Moving "+tracker+" to end of empty list in addNonUrgentMessages");
              if(emptyItemsWithID == null)
                emptyItemsWithID = new DoublyLinkedListImpl<Items>();
              if(parent == null) {
                Logger.error(this, "Tracker is in itemsByID but not in either list! (empty)");
              } else if(parent == emptyItemsWithID) {
                // Normal. Remove it so we can re-add it in the right place.
                emptyItemsWithID.remove(tracker);
              } else if(parent == nonEmptyItemsWithID) {
                Logger.error(this, "Tracker is in non empty items list when is empty");
                nonEmptyItemsWithID.remove(tracker);
              } else
                assert(false);
              addToEmptyBackward(tracker);
            } else {
              if(logDEBUG) Logger.debug(this, "Moving "+tracker+" to end of non-empty list in addNonUrgentMessages");
              if(nonEmptyItemsWithID == null)
                nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
              if(parent == null) {
                Logger.error(this, "Tracker is in itemsByID but not in either list! (non-empty)");
              } else if(parent == nonEmptyItemsWithID) {
                // Normal. Remove it so we can re-add it in the right place.
                nonEmptyItemsWithID.remove(tracker);
              } else if(parent == emptyItemsWithID) {
                Logger.error(this, "Tracker is in empty items list when is non-empty");
                emptyItemsWithID.remove(tracker);
              } else
                assert(false);
              addToNonEmptyBackward(tracker);
            }
          }
        }
        if(mustSendLoadRT && item.sendLoadRT && !addPeerLoadStatsRT.value) {
          addPeerLoadStatsRT.value = true;
          mustSendLoadRT = false;
        } else if(mustSendLoadBulk && item.sendLoadBulk && !addPeerLoadStatsBulk.value) {
          addPeerLoadStatsBulk.value = true;
          mustSendLoadBulk = false;
        }
        if(logMINOR) checkOrder();
       
        if(ret != null) return ret;
      }
      if(logMINOR) checkOrder();
      return null;
    }

    /**
     * Add messages to <code>messages</code> until there are no more
     * messages to add.
     *
     * @param now the current time
     * @param messages the list that messages will be added to
     * @param maxMessages
     * @return the size of <code>messages</code>, multiplied by -1 if there were
     * messages that didn't fit
     */
    private MessageItem addUrgentMessages(long now, MutableBoolean addPeerLoadStatsRT, MutableBoolean addPeerLoadStatsBulk) {
      if(logMINOR) checkOrder();
      MessageItem ret;
      while(true) {
        int lists = 0;
        if(nonEmptyItemsWithID == null) {
          if(logMINOR) Logger.minor(this, "No non-empty items to send, not sending any urgent messages");
          return null;
        }
        lists += nonEmptyItemsWithID.size();
        Items list = nonEmptyItemsWithID.head();
        for(int i=0;i<lists && list != null;i++) {
          if(logMINOR) checkOrder();
          if(list.items.isEmpty()) {
            // Should not happen, but check for it anyway since it keeps happening. :(
            Logger.error(this, "List is in nonEmptyItemsWithID yet it is empty?!: "+list);
            nonEmptyItemsWithID.remove(list);
            addToEmptyBackward(list);
            if(nonEmptyItemsWithID.isEmpty()) {
              if(logMINOR) Logger.minor(this, "Run out of non-empty items to send");
              return null;
            }
            list = nonEmptyItemsWithID.head();
            continue;
          }
          MessageItem item = list.items.getFirst();
          list.items.removeFirst();
          // Move to end of list.
          Items prev = list.getPrev();
          nonEmptyItemsWithID.remove(list);
          item.setDeadline(list.timeLastSent + timeout);
          list.timeLastSent = now;
          if(!list.items.isEmpty()) {
            if(logDEBUG) Logger.debug(this, "Moving "+list+" to end of non empty list in addUrgentMessages");
            addToNonEmptyBackward(list);
          } else {
            if(logDEBUG) Logger.debug(this, "Moving "+list+" to end of empty list in addUrgentMessages");
            addToEmptyBackward(list);
          }
          if(prev == null)
            list = nonEmptyItemsWithID.head();
          else
            list = prev.getNext();
          ret = item;
          if(mustSendLoadRT && item.sendLoadRT && !addPeerLoadStatsRT.value) {
            addPeerLoadStatsRT.value = true;
            mustSendLoadRT = false;
          } else if(mustSendLoadBulk && item.sendLoadBulk && !addPeerLoadStatsBulk.value) {
            addPeerLoadStatsBulk.value = true;
            mustSendLoadBulk = false;
          }
          if(logMINOR) checkOrder();
          if(ret != null) return ret;
        }
        if(logDEBUG)
          Logger.debug(this, "No more messages queued at this priority");
        if(logMINOR) checkOrder();
        return null;
      }
    }

   
    /**
     * Add urgent messages, then non-urgent messages. Add a load message if need to.
     * @param size
     * @param now
     * @param messages
     * @param addPeerLoadStatsRT Will be set if the caller needs to include a load stats message for
     * realtime (i.e. a realtime request completes etc).
     * @param addPeerLoadStatsBulk Will be set if the caller needs to include a load stats message for
     * bulk (i.e. a bulk request completes etc).
     * @param incomplete Will be set if there were more messages but they did not fit. If this is
     * not set, we can try another priority.
     * @return
     */
    MessageItem addPriorityMessages(long now, MutableBoolean addPeerLoadStatsRT, MutableBoolean addPeerLoadStatsBulk) {
      // Urgent messages first.
      if(logMINOR) {
        int nonEmpty = nonEmptyItemsWithID == null ? 0 : nonEmptyItemsWithID.size();
        int empty = emptyItemsWithID == null ? 0 : emptyItemsWithID.size();
        int byID = itemsByID == null ? 0 : itemsByID.size();
        if(nonEmpty + empty < byID) {
          Logger.error(this, "Leaking itemsByID? non empty = "+nonEmpty+" empty = "+empty+" by ID = "+byID+" on "+this);
        } else if(logDEBUG)
          Logger.debug(this, "Items: non empty "+nonEmpty+" empty "+empty+" by ID "+byID+" on "+this);
      }
      if(roundRobinBetweenUIDs)
        moveToUrgent(now);
      clearOldNonUrgent(now);
      if(roundRobinBetweenUIDs) {
        MessageItem item = addUrgentMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
        if(item != null) return item;
      } else {
        assert(itemsByID == null);
      }
      //   If no more urgent messages, try to add some non-urgent messages too.
      return addNonUrgentMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
    }

    private void clearOldNonUrgent(long now) {
      if(logMINOR) checkOrder();
      int removed = 0;
      if(emptyItemsWithID == null) return;
      while(true) {
        if(logMINOR) checkOrder();
        if(emptyItemsWithID.isEmpty()) return;
        Items list = emptyItemsWithID.head();
        if(!list.items.isEmpty()) {
          // FIXME remove paranoia
          Logger.error(this, "List with items in emptyItemsWithID!!");
          emptyItemsWithID.remove(list);
          addToNonEmptyBackward(list);
          return;
        }
        if(list.timeLastSent == -1 || now - list.timeLastSent > FORGET_AFTER) {
          // FIXME: Urgh, what a braindead API! remove(Object) on a Map<Long, Items> !?!?!?!
          // Anyway we'd better check the return value!
          Items old = itemsByID.remove(list.id);
          if(old == null)
            Logger.error(this, "List was not in the items by ID tracker: "+list.id);
          else if(old != list)
            Logger.error(this, "Different list in the items by ID tracker: "+old+" not "+list+" for "+list.id);
          emptyItemsWithID.remove(list);
          removed++;
        } else {
          if(logDEBUG && removed > 0)
            Logger.debug(this, "Removed "+removed+" old empty UID trackers");
          break;
        }
      }
    }

    public void clear() {
      emptyItemsWithID = null;
      nonEmptyItemsWithID = null;
      itemsByID = null;
      itemsNonUrgent = null;
      if(logMINOR) checkOrder();
    }

    public boolean removeMessage(MessageItem item) {
      if(logMINOR) checkOrder();
      long id = item.getID();
      Items list;
      if(itemsByID != null) {
        list = itemsByID.get(id);
        if(list != null) {
          if(list.remove(item)) {
            if(list.items.isEmpty()) {
              nonEmptyItemsWithID.remove(list);
              addToEmptyBackward(list);
            }
            if(logMINOR) checkOrder();
            return true;
          }
        }
      }
      if(logMINOR) checkOrder();
      if(itemsNonUrgent != null)
        return itemsNonUrgent.remove(item);
      else
        return false;
    }
   
    public void removeUIDs(Long[] list) {
      if(logMINOR) checkOrder();
      if(itemsByID == null) return;
      for(Long l : list) {
        Items items = itemsByID.get(l);
        if(items == null) continue;
        if(items.items.isEmpty()) {
          itemsByID.remove(l);
          assert(emptyItemsWithID != null);
          assert(items.getParent() == emptyItemsWithID);
          emptyItemsWithID.remove(items);
        }
      }
      if(logMINOR) checkOrder();
    }

    public boolean isEmpty() {
      if(itemsNonUrgent != null && !itemsNonUrgent.isEmpty()) {
        return false;
      }
      if(nonEmptyItemsWithID != null) {
        for(Items items : nonEmptyItemsWithID) {
          if(items.items.size() == 0) continue;
          return false;
        }
      }
      return true;
    }

  }

  PeerMessageQueue() {
    queuesByPriority = new PrioQueue[DMT.NUM_PRIORITIES];
    for(int i=0;i<queuesByPriority.length;i++) {
      if(i == DMT.PRIORITY_BULK_DATA)
        // Bulk: round-robin between UID's (timeout since last sent), long timeout.
        queuesByPriority[i] = new PrioQueue(PacketSender.MAX_COALESCING_DELAY_BULK, true);
      else if(i == DMT.PRIORITY_REALTIME_DATA)
        // Realtime: round-robin between UID's (timeout since last sent), short timeout.
        queuesByPriority[i] = new PrioQueue(PacketSender.MAX_COALESCING_DELAY, true);
      else
        // Everything else: Still round-robin between UID's, but timeout on submitted.
        queuesByPriority[i] = new PrioQueue(PacketSender.MAX_COALESCING_DELAY, false);
    }
  }

  /**
   * Queue a <code>MessageItem</code> and return an estimate of the size of
   * this queue. The value returned is the estimated number of bytes
   * needed for sending the all messages in this queue. Note that if the
   * returned estimate is higher than 1024, it might not cover all messages.
   * @param item the <code>MessageItem</code> to queue
   * @return an estimate of the size of this queue
   */
  public synchronized int queueAndEstimateSize(MessageItem item, int maxSize) {
    enqueuePrioritizedMessageItem(item);
    int x = 0;
    for(PrioQueue pq : queuesByPriority) {
      if(pq.itemsNonUrgent != null) {
        for(MessageItem it : pq.itemsNonUrgent) {
          x += it.getLength() + 2;
          if(x > maxSize)
            break;
        }
      }
      if(pq.nonEmptyItemsWithID != null) {
        for(PrioQueue.Items q : pq.nonEmptyItemsWithID)
          for(MessageItem it : q.items) {
            x += it.getLength() + 2;
            if(x > maxSize)
              break;
          }
      }
    }
    return x;
  }

  public synchronized long getMessageQueueLengthBytes() {
    long x = 0;
    for(PrioQueue pq : queuesByPriority) {
      if(pq.nonEmptyItemsWithID != null)
        for(PrioQueue.Items q : pq.nonEmptyItemsWithID)
          for(MessageItem it : q.items)
            x += it.getLength() + 2;
    }
    return x;
  }

  private synchronized void enqueuePrioritizedMessageItem(MessageItem addMe) {
    //Assume it goes on the end, both the common case
    short prio = addMe.getPriority();
    queuesByPriority[prio].addLast(addMe);
    if(addMe.sendLoadRT)
      mustSendLoadRT = true;
    if(addMe.sendLoadBulk)
      mustSendLoadBulk = true;
  }

  /**
   * like enqueuePrioritizedMessageItem, but adds it to the front of those in the same priority.
   *
   * WARNING: Pulling a message and then pushing it back will mess up the fairness
   * between UID's send order. Try to avoid it.
   */
  synchronized void pushfrontPrioritizedMessageItem(MessageItem addMe) {
    //Assume it goes on the front
    short prio = addMe.getPriority();
    queuesByPriority[prio].addFirst(addMe);
    if(addMe.sendLoadRT)
      mustSendLoadRT = true;
    if(addMe.sendLoadBulk)
      mustSendLoadBulk = true;
  }

  public synchronized MessageItem[] grabQueuedMessageItems() {
    int size = 0;
    for(PrioQueue queue : queuesByPriority)
      size += queue.size();
    MessageItem[] output = new MessageItem[size];
    int ptr = 0;
    for(PrioQueue queue : queuesByPriority) {
      ptr = queue.addTo(output, ptr);
      queue.clear();
    }
    return output;
  }

  /**
   * Get the time at which the next message must be sent. If any message is
   * overdue, we will return a value less than now, which may not be completely
   * accurate.
   * @param t The current next urgent time. The return value will be no greater
   * than this.
   * @param returnIfBefore The current time. If the next urgent time is less than
   * this we return immediately rather than computing an accurate past value.
   * Set to Long.MAX_VALUE if you want an accurate value.
   * @return The next urgent time, but can be too high if it is less than now.
   */
  public synchronized long getNextUrgentTime(long t, long returnIfBefore) {
    for(PrioQueue queue: queuesByPriority) {
      t = Math.min(t, queue.getNextUrgentTime(t, returnIfBefore));
      if(t <= returnIfBefore) return t; // How much in the past doesn't matter, as long as it's in the past.
    }
    return t;
  }

  /**
   * Returns <code>true</code> if there are messages that will timeout before
   * <code>now</code>.
   * @param now the timeout for messages waiting to be sent
   * @return <code>true</code> if there are messages that will timeout before
   * <code>now</code>
   */
  public boolean mustSendNow(long now) {
    return getNextUrgentTime(Long.MAX_VALUE, now) <= now;
  }

  /**
   * Returns <code>true</code> if <code>minSize</code> + the length of all
   * messages in this queue is greater than <code>maxSize</code>.
   * @param minSize the starting size
   * @param maxSize the maximum size
   * @return <code>true</code> if <code>minSize</code> + the length of all
   * messages in this queue is greater than <code>maxSize</code>
   */
  public synchronized boolean mustSendSize(int minSize, int maxSize) {
    int length = minSize;
    for(PrioQueue items : queuesByPriority) {
      length = items.addSize(length, maxSize);
      if(length > maxSize) return true;
    }
    return false;
  }

  /** Grab a message to send. WARNING: PeerMessageQueue not only removes the message,
   * it assumes it has been sent for purposes of fairness between UID's. You should try
   * not to call this function if you are not going to be able to send the message:
   * check in advance if possible. */
  public synchronized MessageItem grabQueuedMessageItem(int minPriority) {
    long now = System.currentTimeMillis();
   
    MutableBoolean addPeerLoadStatsRT = new MutableBoolean();
    MutableBoolean addPeerLoadStatsBulk = new MutableBoolean();
   
    addPeerLoadStatsRT.value = true;
    addPeerLoadStatsBulk.value = true;
   
    for(int i=0;i<DMT.PRIORITY_REALTIME_DATA;i++) {
      if(i < minPriority) continue;
      if(logMINOR) Logger.minor(this, "Adding from priority "+i);
      MessageItem ret = queuesByPriority[i].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
    }
   
    // Include bulk or realtime, whichever is more urgent.
   
    boolean tryRealtimeFirst = true;
   
    // If one is empty, try the other.
    // Otherwise try whichever is more urgent, favouring realtime if there is a draw.
    // Realtime is supposed to be bursty.
   
    if(queuesByPriority[DMT.PRIORITY_REALTIME_DATA].isEmpty()) {
      tryRealtimeFirst = false;
    } else if(queuesByPriority[DMT.PRIORITY_BULK_DATA].isEmpty()) {
      tryRealtimeFirst = true;
    } else if(queuesByPriority[DMT.PRIORITY_BULK_DATA].getNextUrgentTime(Long.MAX_VALUE, 0) >= queuesByPriority[DMT.PRIORITY_REALTIME_DATA].getNextUrgentTime(Long.MAX_VALUE, 0)) {
      tryRealtimeFirst = true;
    } else {
      tryRealtimeFirst = false;
    }
   
    // FIXME token bucket?
    if(tryRealtimeFirst) {
      // Try realtime first
      if(logMINOR) Logger.minor(this, "Trying realtime first");
      MessageItem ret = queuesByPriority[DMT.PRIORITY_REALTIME_DATA].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
      if(logMINOR) Logger.minor(this, "Trying bulk");
      ret = queuesByPriority[DMT.PRIORITY_BULK_DATA].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
    } else {
      // Try bulk first
      if(logMINOR) Logger.minor(this, "Trying bulk first");
      MessageItem ret = queuesByPriority[DMT.PRIORITY_BULK_DATA].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
      if(logMINOR) Logger.minor(this, "Trying realtime");
      ret = queuesByPriority[DMT.PRIORITY_REALTIME_DATA].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
    }
    for(int i=DMT.PRIORITY_BULK_DATA+1;i<DMT.NUM_PRIORITIES;i++) {
      if(i < minPriority) continue;
      if(logMINOR) Logger.minor(this, "Adding from priority "+i);
      MessageItem ret = queuesByPriority[i].addPriorityMessages(now, addPeerLoadStatsRT, addPeerLoadStatsBulk);
      if(ret != null) return ret;
    }
    // Nothing to send.
    return null;
  }
 
  public boolean removeMessage(MessageItem message) {
    synchronized(this) {
      short prio = message.getPriority();
      if(!queuesByPriority[prio].removeMessage(message)) return false;
    }
    message.onFailed();
    return true;
  }

  public synchronized void removeUIDsFromMessageQueues(Long[] list) {
    for(PrioQueue queue : queuesByPriority) {
      queue.removeUIDs(list);
    }
  }
}
TOP

Related Classes of freenet.node.PeerMessageQueue$PrioQueue$Items

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.