package jade.core.messaging; //import java.util.Hashtable; import jade.util.leap.List; import jade.util.leap.LinkedList; import jade.util.leap.RoundList; import jade.util.leap.Map; import jade.util.leap.HashMap; import jade.core.AID; import jade.core.messaging.MessageManager.PendingMsg; import jade.core.messaging.MessageManager.Channel; import jade.lang.acl.ACLMessage; import jade.util.Logger; /** * Object to mantain message to send and * to preserve the order for sending. * * @author Elisabetta Cortese - TILAB */ class OutBox { private int size = 0; private int maxSize; private boolean overMaxSize = false; // The massages to be delivered organized as an hashtable that maps // a receiver AID into the Box of messages to be delivered to that receiver private final Map messagesByReceiver = new HashMap(); // The messages to be delivered organized as a round list of the Boxes of // messages for the currently addressed receivers private final RoundList messagesByOrder = new RoundList(); // For debugging purposes private long submittedCnt = 0; private long servedCnt = 0; private Logger myLogger; OutBox(int s) { maxSize = s; myLogger = Logger.getMyLogger(getClass().getName()); } /** * Add a message to the tail of the Box of messages for the indicated * receiver. If a Box for the indicated receiver is not yet present, a * new one is created. * This method is executed by an agent's thread requesting to deliver * a new message. */ void addLast(AID receiverID, GenericMessage msg, Channel ch) { boolean logActivated = myLogger.isLoggable(Logger.FINER); if (logActivated) myLogger.log(Logger.FINER,"Entering addLast for receiver "+receiverID.getName()); if (msg.getPayload() != null && msg.isModifiable()) { ACLMessage acl = msg.getACLMessage(); if (acl != null) { acl.setContent(null); } } // This must fall outside the synchronized block because the method calls Thread.sleep increaseSize(msg.length()); synchronized (this) { Box b = (Box) messagesByReceiver.get(receiverID); if (logActivated) { String msgDebug = (b==null)? "No box for receiver "+receiverID.getName():"Box for receiver "+receiverID.getName()+" busy ? "+b.isBusy(); myLogger.log(Logger.FINER,msgDebug); } if (b == null){ // There is no Box of messages for this receiver yet. Create a new one b = new Box(receiverID); messagesByReceiver.put(receiverID, b); messagesByOrder.add(b); if (logActivated) myLogger.log(Logger.FINER,"Box created for receiver "+receiverID.getName()); } if (logActivated) myLogger.log(Logger.FINER,"Message entered in box for receiver "+receiverID.getName()); b.addLast(new PendingMsg(msg, receiverID, ch, -1)); submittedCnt++; // Wakes up all deliverers notifyAll(); } if (logActivated) myLogger.log(Logger.FINER,"Exiting addLast for receiver "+receiverID.getName()); } /** * Add a message to the head of the Box of messages for the indicated * receiver. * This method is executed by the TimerDispatcher Thread when a * retransmission timer expires. Therefore a Box of messages for the * indicated receiver must already exist. Moreover the busy flag of * this Box must be reset to allow deliverers to handle messages in it * synchronized void addFirst(PendingMsg pm){ Box b = (Box) messagesByReceiver.get(pm.getReceiver()); b.addFirst(pm); b.setBusy(false); // Wakes up all deliverers notifyAll(); }*/ /** * Get the first message for the first idle (i.e. not busy) receiver. * This is executed by a Deliverer thread just before delivering * a message. */ synchronized final PendingMsg get(){ Box b = null; // Wait until an idle (i.e. not busy) receiver is found while( (b = getNextIdle()) == null ){ try{ if (myLogger.isLoggable(Logger.FINER)) { myLogger.log(Logger.FINER, "Deliverer "+Thread.currentThread()+" go to sleep..."); } wait(); if (myLogger.isLoggable(Logger.FINER)) { myLogger.log(Logger.FINER, "Deliverer "+Thread.currentThread()+" wake up"); } } catch (InterruptedException ie) { // Just do nothing } } PendingMsg pm = b.removeFirst(); decreaseSize(pm.getMessage().length()); return pm; } /** * Get the Box of messages for the first idle (i.e. not busy) receiver. * @return null if all receivers are currently busy * This method does not need to be synchronized as it is only executed * inside a synchronized block. */ private final Box getNextIdle(){ for (int i = 0; i < messagesByOrder.size(); ++i) { Box b = (Box) messagesByOrder.get(); if (!b.isBusy()) { b.setBusy(true); if( myLogger.isLoggable(Logger.FINER) ) myLogger.log(Logger.FINER,"Setting box busy for receiver "+b.getReceiver().getName()); return b; } } return null; } /** * A message for the receiver receiverID has been served * If the Box of messages for that receiver is now empty --> remove it. * Otherwise just mark it as idel (not busy). */ synchronized final void handleServed( AID receiverID ){ servedCnt++; boolean logActivated = myLogger.isLoggable(Logger.FINER); if (logActivated) myLogger.log(Logger.FINER,"Entering handleServed for "+receiverID.getName()); Box b = (Box) messagesByReceiver.get(receiverID); if (b.isEmpty()) { messagesByReceiver.remove(receiverID); messagesByOrder.remove(b); if (logActivated) myLogger.log(Logger.FINER,"Removed box for receiver "+receiverID.getName()); } else { b.setBusy(false); if (logActivated) myLogger.log(Logger.FINER,"Freeing box for receiver "+receiverID.getName()); } if (logActivated) myLogger.log(Logger.FINER,"Exiting handleServed for "+receiverID.getName()); } private void increaseSize(int k) { long sleepTime = 0; synchronized (this) { size += k; if (size > maxSize) { if (!overMaxSize) { myLogger.log(Logger.WARNING, "MessageManager queue size > "+maxSize); overMaxSize = true; } sleepTime = (1 + ((size - maxSize) / 1000000)) * 100; } } if (sleepTime > 0) { try { // delay a bit this Thread because the queue is becoming too big Thread.sleep(sleepTime); } catch (InterruptedException ie) {} } } /** * The method decreases the value of size and, eventually, * set to false the value of overMaxSize. * <p> * This method should have been declared synchronized. * For possible better peformance, it is not declared synchronized because * it is a private method and it is called only * by the method get() which is already synchronized. * @param k the value by which size must be decremented */ private void decreaseSize(int k) { size -= k; if (size < maxSize) { if (overMaxSize) { myLogger.log(Logger.INFO, "MessageManager queue size < "+maxSize); overMaxSize = false; } } } /** * This class represents a Box of messages to be delivered to * a single receiver */ private class Box { private final AID receiver; private boolean busy; private String owner; private final List messages; public Box(AID r) { receiver = r; busy = false; messages = new LinkedList(); } private AID getReceiver() { return receiver; } private void setBusy(boolean b){ busy = b; //#J2ME_EXCLUDE_BEGIN owner = (busy ? Thread.currentThread().getName() : null); //#J2ME_EXCLUDE_END } private boolean isBusy(){ return busy; } private void addLast(PendingMsg pm) { messages.add(pm); } private PendingMsg removeFirst() { return (PendingMsg) messages.remove(0); } private boolean isEmpty() { return messages.isEmpty(); } // For debugging purpose public String toString() { return "("+receiver.getName()+" :busy "+busy+ (owner != null ? " :owner "+owner : "") + " :message-cnt "+messages.size()+")"; } } // END of inner class Box // For debugging purpose synchronized String[] getStatus() { Object[] boxes = messagesByOrder.toArray(); String[] status = new String[boxes.length]; for (int i = 0; i < boxes.length; ++i) { status[i] = boxes[i].toString(); } return status; } // For debugging purpose int getSize() { return size; } long getSubmittedCnt() { return submittedCnt; } long getServedCnt() { return servedCnt; } }