/***************************************************************** JADE - Java Agent DEvelopment Framework is a framework to develop multi-agent systems in compliance with the FIPA specifications. Copyright (C) 2000 CSELT S.p.A. GNU Lesser General Public License This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *****************************************************************/ package jade.core.messaging; import jade.util.Logger; import jade.lang.acl.ACLMessage; import jade.domain.FIPAAgentManagement.InternalError; import jade.core.AID; import jade.core.ResourceManager; import jade.core.Profile; import jade.core.ProfileException; import jade.core.NotFoundException; import jade.core.UnreachableException; /** * This class manages the delivery of ACLMessages to remote destinations * in an asynchronous way. * If network problems prevent the delivery of a message, this class also * embeds a mechanism to buffer the message and periodically retry to * deliver it. * @author Giovanni Caire - TILAB * @author Elisabetta Cortese - TILAB * @author Fabio Bellifemine - TILAB * @author Jerome Picault - Motorola Labs * @version $Date: 2008-08-19 12:27:27 +0200 (mar, 19 ago 2008) $ $Revision: 6043 $ */ class MessageManager { public interface Channel { void deliverNow(GenericMessage msg, AID receiverID) throws UnreachableException, NotFoundException; void notifyFailureToSender(GenericMessage msg, AID receiver, InternalError ie); } // A shared instance to have a single thread pool private static MessageManager theInstance; // FIXME: Maybe a table, indexed by a profile subset, would be better? private static final int POOL_SIZE_DEFAULT = 5; private static final int MAX_POOL_SIZE = 100; private static final int MAX_QUEUE_SIZE_DEFAULT = 10000000; // 10MBytes private OutBox outBox; private Thread[] delivererThreads; private Deliverer[] deliverers; private Logger myLogger = Logger.getMyLogger(getClass().getName()); private MessageManager() { } public static synchronized MessageManager instance(Profile p) { if(theInstance == null) { theInstance = new MessageManager(); theInstance.initialize(p); } return theInstance; } public void initialize(Profile p) { // POOL_SIZE int poolSize = POOL_SIZE_DEFAULT; try { String tmp = p.getParameter("jade_core_messaging_MessageManager_poolsize", null); poolSize = Integer.parseInt(tmp); } catch (Exception e) { // Do nothing and keep default value } // OUT_BOX_MAX_SIZE int maxQueueSize = MAX_QUEUE_SIZE_DEFAULT; try { String tmp = p.getParameter("jade_core_messaging_MessageManager_maxqueuesize", null); maxQueueSize = Integer.parseInt(tmp); } catch (Exception e) { // Do nothing and keep default value } outBox = new OutBox(maxQueueSize); try { ResourceManager rm = p.getResourceManager(); delivererThreads = new Thread[poolSize]; deliverers = new Deliverer[poolSize]; for (int i = 0; i < poolSize; ++i) { String name = "Deliverer-"+i; deliverers[i] = new Deliverer(); delivererThreads[i] = rm.getThread(ResourceManager.TIME_CRITICAL, name, deliverers[i]); if (myLogger.isLoggable(Logger.FINE)) { myLogger.log(Logger.FINE, "Starting deliverer "+name+". Thread="+delivererThreads[i]); } delivererThreads[i].start(); } } catch (ProfileException pe) { throw new RuntimeException("Can't get ResourceManager. "+pe.getMessage()); } } /** Activate the asynchronous delivery of a GenericMessage */ public void deliver(GenericMessage msg, AID receiverID, Channel ch) { outBox.addLast(receiverID, msg, ch); } /** Inner class Deliverer */ class Deliverer implements Runnable { // For debugging purpose private long servedCnt = 0; public void run() { while (true) { // Get a message from the OutBox (block until there is one) PendingMsg pm = outBox.get(); GenericMessage msg = pm.getMessage(); AID receiverID = pm.getReceiver(); // Deliver the message Channel ch = pm.getChannel(); try { ch.deliverNow(msg, receiverID); } catch (Throwable t) { // A MessageManager deliverer thread must never die myLogger.log(Logger.WARNING, "MessageManager cannot deliver message "+stringify(msg)+" to agent "+receiverID.getName(), t); ch.notifyFailureToSender(msg, receiverID, new InternalError(ACLMessage.AMS_FAILURE_UNEXPECTED_ERROR + ": "+t)); } servedCnt++; outBox.handleServed(receiverID); } } long getServedCnt() { return servedCnt; } } // END of inner class Deliverer /** Inner class PendingMsg */ public static class PendingMsg { private final GenericMessage msg; private final AID receiverID; private final Channel channel; private long deadline; public PendingMsg(GenericMessage msg, AID receiverID, Channel channel, long deadline) { this.msg = msg; this.receiverID = receiverID; this.channel = channel; this.deadline = deadline; } public GenericMessage getMessage() { return msg; } public AID getReceiver() { return receiverID; } public Channel getChannel() { return channel; } public long getDeadline() { return deadline; } public void setDeadline(long deadline) { this.deadline = deadline; } } // END of inner class PendingMsg /** */ public static final String stringify(GenericMessage m) { ACLMessage msg = m.getACLMessage(); if (msg != null) { StringBuffer sb = new StringBuffer("("); sb.append(ACLMessage.getPerformative(msg.getPerformative())); sb.append(" sender: "); sb.append(msg.getSender().getName()); if (msg.getOntology() != null) { sb.append(" ontology: "); sb.append(msg.getOntology()); } if (msg.getConversationId() != null) { sb.append(" conversation-id: "); sb.append(msg.getConversationId()); } sb.append(')'); return sb.toString(); } else { return ("unavailable"); } } // For debugging purpose String[] getQueueStatus() { return outBox.getStatus(); } // For debugging purpose String getGlobalInfo() { return "Submitted-messages = "+outBox.getSubmittedCnt()+", Served-messages = "+outBox.getServedCnt()+", Queue-size (byte) = "+outBox.getSize(); } // For debugging purpose String[] getThreadPoolStatus() { String[] status = new String[delivererThreads.length]; for (int i = 0; i < delivererThreads.length; ++i) { status[i] = "(Deliverer-"+i+" :alive "+delivererThreads[i].isAlive()+" :Served-messages "+deliverers[i].getServedCnt()+")"; } return status; } // For debugging purpose Thread[] getThreadPool() { return delivererThreads; } }