All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.dyade.aaa.agent.Engine Maven / Gradle / Ivy

There is a newer version: 5.22.0-EFLUID
Show newest version
/*
 * Copyright (C) 2001 - 2024 ScalAgent Distributed Technologies
 * Copyright (C) 1996 - 2000 BULL
 * Copyright (C) 1996 - 2000 INRIA
 *
 * 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; either
 * version 2.1 of the License, or any later version.
 * 
 * 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 fr.dyade.aaa.agent;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Timer;
import java.util.Vector;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.AverageCPUTask;
import fr.dyade.aaa.common.AverageLoadTask;
import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.common.Queue;

class EngineThread extends Thread {
  Engine engine = null;

  EngineThread(Engine engine) {
    super(AgentServer.getThreadGroup(), engine, engine.getName());
    this.engine = engine;
  }
}

/**
 * The Engine class provides multiprogramming of agents. It
 * realizes the program loop which successively gets the notifications from
 * the message queue and calls the relevant reaction function member of the
 * target agent. The engine's basic behaviour is:
 * 
 * While (true) {
 *   // get next message in channel
 *   Message msg = qin.get();
 *   // get the agent to process event
 *   Agent agent = load(msg.to);
 *   // execute relevant reaction, all notification sent during this
 *   // reaction is inserted into persistent queue in order to processed
 *   // by the channel.
 *   agent.react(msg.from, msg.not);
 *   // save changes, then commit.
 *   <BEGIN TRANSACTION>
 *   qin.pop();
 *   channel.dispatch();
 *   agent.save();
 *   <COMMIT TRANSACTION>
 * }
 * 
*

* The Engine class ensures the atomic handling of an agent * reacting to a notification: *

    *
  • if the reaction completes, a COMMIT ensures all changes related to * the reaction are committed (state change of the agent, notifications * signaled during the reaction, deletion of the handled notification); *
  • if anything goes wrong during the reaction, a ROLLBACK undoes the * changes; depending on the error kind it may be necessary to execute * additional operations to resynchronize the database and the memory * objects, and to allow the main program to continue. *
*
* Handling errors.

* Two types of errors may occur: errors of first type are detected in the * source code and signaled by an Exception; serious errors lead * to an Error being raised then the engine exits. In the first * case the exception may be handled at any level, even partially. Most of * them are signaled up to the engine loop. Two cases are then distinguished * depending on the recovery policy:

    *
  • if recoveryPolicy is set to RP_EXC_NOT * (default value) then the agent state and the message queue are restored * (ROLLBACK); an ExceptionNotification notification is sent * to the sender and the engine may then proceed with next notification; *
  • if recoveryPolicy is set to RP_EXIT the engine * stops the agent server. *
*/ class Engine implements Runnable, AgentEngine, EngineMBean { /** * Queue of messages to be delivered to local agents. */ protected MessageQueue qin; /** * Boolean variable used to stop the engine properly. The engine tests * this variable between each reaction, and stops if it is false. */ protected volatile boolean isRunning; /** * Boolean variable used to stop the engine properly. If this variable * is true then the engine is waiting and it can interrupted, else it * handles a notification and it will exit after (the engine tests the * isRunning variable between * each reaction) */ protected volatile boolean canStop; /** Logical timestamp information for messages in "local" domain. */ private int stamp; /** Buffer used to optimize */ private byte[] stampBuf = null; /** True if the timestamp is modified since last save. */ private boolean modified = false; /** This table is used to maintain a list of agents already in memory * using the AgentId as primary key. */ Hashtable agents; /** Virtual time counter use in FIFO swap-in/swap-out mechanisms. */ long now = 0; /** Maximum number of memory loaded agents. */ int NbMaxAgents = 1000; /** * Flag to avoid transaction when not needed. * By default true, we run a transaction only if there is transaction operations: * - incoming persistent notification, * - persistent objects modifications, * - outgoing persistent notifications. * The value could be set using "NoTxIfTransient" property. */ boolean noTxIfTransient; /** * Returns the number of agent's reaction since last boot. * * @return the number of agent's reaction since last boot */ public long getNbReactions() { return now; } /** * Returns the maximum number of agents loaded in memory. * * @return the maximum number of agents loaded in memory */ public int getNbMaxAgents() { return NbMaxAgents; } /** * Sets the maximum number of agents that can be loaded simultaneously * in memory. * * @param NbMaxAgents the maximum number of agents */ public void setNbMaxAgents(int NbMaxAgents) { this.NbMaxAgents = NbMaxAgents; } /** * Returns the number of agents actually loaded in memory. * * @return the maximum number of agents actually loaded in memory */ public int getNbAgents() { return agents.size(); } /** * Gets the number of messages posted to this engine since creation. * * return the number of messages. */ public int getNbMessages() { return stamp; } /** * Gets the number of waiting messages in this engine. * * return the number of waiting messages. */ public int getNbWaitingMessages() { return qin.size(); } /** Vector containing id's of all fixed agents. */ Vector fixedAgentIdList = null; /** * Returns the number of fixed agents. * * @return the number of fixed agents */ public int getNbFixedAgents() { return fixedAgentIdList.size(); } /** * Returns the flag to avoid transactions. * * @return the flag to avoid transactions */ public boolean isNoTxIfTransient() { return noTxIfTransient; } /** * The current agent running. */ Agent agent = null; /** * Returns the unique id. of the running agent if any, null otherwise. * * @return the unique id. of the running agent if any, null otherwise. */ public String getRunningAgent() { // Gets a copy of reference to avoid NPE Agent ag = agent; if (ag != null) return ag.getAgentId(); return null; } /** * The message in progress. */ Message msg = null; /** * The active component of this engine. */ EngineThread thread = null; /** * Send ExceptionNotification notification in case of exception * in agent specific code. * Constant value for the recoveryPolicy variable. */ static final int RP_EXC_NOT = 0; /** * Stop agent server in case of exception in agent specific code. * Constant value for the recoveryPolicy variable. */ static final int RP_EXIT = 1; /** * String representations of RP_* constant values * for the recoveryPolicy variable. */ static final String[] rpStrings = { "notification", "exit" }; /** * recovery policy in case of exception in agent specific code. * Default value is RP_EXC_NOT. */ int recoveryPolicy = RP_EXIT; private String name; /** * Returns this Engine's name. * * @return this Engine's name. */ public final String getName() { return name; } /** * Returns the corresponding domain's name. * * @return this domain's name. */ public final String getDomainName() { return "engine"; } /** * Creates a new instance of Engine (real class depends of server type). * * @return the corresponding engine's instance. * @throws Exception an error occurs. */ static Engine newInstance() throws Exception { String cname = "fr.dyade.aaa.agent.Engine"; cname = AgentServer.getProperty("Engine", cname); Class eclass = Class.forName(cname); return (Engine) eclass.newInstance(); } protected Queue mq; private boolean persistentPush; /** * Push a new message in temporary queue until the end of current reaction. * As this method is only called by engine's thread it does not need to be * synchronized. */ public final void push(AgentId from, AgentId to, Notification not) { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ", push(" + from + ", " + to + ", " + not + ")"); if ((to == null) || to.isNullId()) return; if (not.persistent) { persistentPush = true; } mq.push(Message.alloc(from, to, not)); } /** * Push a new message in temporary queue until the end of current reaction. * As this method is only called by engine's thread it does not need to be * synchronized. */ public final void push(AgentId to, Notification not) { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ", push(" + to + ", " + not + ")"); if (Thread.currentThread() == thread) { push(agent.getId(), to, not); } else { Channel.channel.directSendTo(AgentId.localId, to, not); } } /** * Dispatch messages between the * MessageConsumer: * Engine component and * Network components.

* Handle persistent information in respect with engine transaction. *


* Be careful, this method must only be used during a transaction in * order to ensure the mutual exclusion. * * @exception IOException error when accessing the local persistent * storage. */ final void dispatch() throws Exception { Message msg = null; while (! mq.isEmpty()) { try { msg = (Message) mq.get(); } catch (InterruptedException exc) { continue; } if (msg.from == null) msg.from = AgentId.localId; Channel.post(msg); // if (AgentServer.sdf != null) { // // SDF generation // strbuf.append("\n"); // strbuf.append("\" info=\"").append(msg.not.getClass().getSimpleName()); // strbuf.append("" + msg.not + "\n"); // strbuf.append("\n"); // } // // // TODO (AF): Generates the appropriate code for SDF // if (AgentServer.logsdf.isLoggable(BasicLevel.INFO)) // AgentServer.logsdf.log(BasicLevel.INFO, ""); mq.pop(); } Channel.save(); } /** * Cleans the Channel queue of all pushed notifications. *
* Be careful, this method must only be used during a transaction in * order to ensure the mutual exclusion. */ final void clean() { mq.clear(); } protected Logger logmon = null; /** * Initializes a new Engine object (can only be used by subclasses). * @throws Exception an error occurs. */ protected Engine() throws Exception { name = "Engine#" + AgentServer.getServerId(); // Get the logging monitor from current server MonologLoggerFactory logmon = Debug.getLogger(Engine.class.getName() + ".#" + AgentServer.getServerId()); if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + " created [" + getClass().getName() + "]."); NbMaxAgents = AgentServer.getInteger("NbMaxAgents", NbMaxAgents).intValue(); qin = new MessageVector(name, AgentServer.getTransaction().isPersistent()); mq = new Queue(); agentProfiling = AgentServer.getBoolean("AgentProfiling"); String noTxIfTransientValue = AgentServer.getProperty("NoTxIfTransient"); if (noTxIfTransientValue != null) { noTxIfTransient = Boolean.parseBoolean(noTxIfTransientValue); } else { // Default value noTxIfTransient = true; } isRunning = false; canStop = false; thread = null; needToBeCommited = false; restore(); if (modified) save(); } public void init(AgentEngineContext agentEngineContext) throws Exception { // Before any agent may be used, the environment, including the hash table, // must be initialized. agents = new Hashtable<>(); try { // Creates or initializes AgentFactory, then loads and initializes // all fixed agents. fixedAgentIdList = (Vector) AgentServer.getTransaction().load(getName() + ".fixed"); if (fixedAgentIdList == null) { // It's the first launching of this engine, in other case there is // at least the factory in fixedAgentIdList. fixedAgentIdList = new Vector<>(); // Creates factory AgentFactory factory = new AgentFactory(AgentId.factoryId); createAgent(AgentId.factoryId, factory); factory.save(); logmon.log(BasicLevel.INFO, getName() + ", factory created"); } // loads all fixed agents for (int i=0; i e = agents.elements() ; e.hasMoreElements() ;) { ag[i++] = e.nextElement(); } for (i--; i>=0; i--) { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, "Garbages Agent" + ag[i].id + " [" + ag[i].name + "]."); agents.remove(ag[i].id); try { // Set current agent running in order to allow from field fixed // for sendTo during agentFinalize (We assume that only Engine // use this method). agent = ag[i]; ag[i].agentFinalize(false); } catch (Exception exc) { logmon.log(BasicLevel.ERROR, "Agent" + ag[i].id + " [" + ag[i].name + "] error during agentFinalize.", exc); } finally { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, "Agent" + ag[i].id + " [" + ag[i].name + "] garbaged."); agent = null; } ag[i] = null; } } /** * Creates and initializes an agent. * * @param agent agent object to create * * @exception Exception * unspecialized exception */ public final void createAgent(AgentId id, Agent agent) throws Exception { agent.id = id; agent.deployed = true; agent.agentInitialize(true); createAgent(agent); } /** * Creates and initializes an agent. * * @param agent agent object to create * * @exception Exception * unspecialized exception */ final void createAgent(Agent agent) throws Exception { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ", creates: " + agent); if (agent.isFixed()) { // Subscribe the agent in pre-loading list. addFixedAgentId(agent.getId()); } if (agent.logmon == null) agent.logmon = Debug.getLogger(Agent.class.getName()); agent.save(); // Memorize the agent creation and ... now += 1; garbage(); agents.put(agent.getId(), agent); } /** * Deletes an agent. * * @param id unique identifier of agent to delete */ public void deleteAgent(AgentId id) throws Exception { Agent ag; Agent old = agent; try { ag = load(id); if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ", delete Agent" + ag.id + " [" + ag.name + "]"); AgentServer.getTransaction().delete(ag.id.toString()); } catch (UnknownAgentException exc) { logmon.log(BasicLevel.ERROR, getName() + ", can't delete unknown Agent" + id); throw new Exception("Can't delete unknown Agent" + id); } catch (Exception exc) { logmon.log(BasicLevel.ERROR, getName() + ", can't delete Agent" + id, exc); throw new Exception("Can't delete Agent" + id); } if (ag.isFixed()) removeFixedAgentId(ag.id); agents.remove(ag.getId()); try { // Set current agent running in order to allow from field fixed // for sendTo during agentFinalize (We assume that only Engine // use this method). agent = ag; ag.agentFinalize(true); } catch (Exception exc) { logmon.log(BasicLevel.ERROR, "Agent" + ag.id + " [" + ag.name + "] error during agentFinalize", exc); } finally { agent = old; } } /** * The garbage method should be called regularly , to swap out * from memory all the agents which have not been accessed for a time. */ void garbage() { if (! AgentServer.getTransaction().isPersistent()) return; if (agents.size() < (NbMaxAgents + fixedAgentIdList.size())) return; if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ", garbage: " + agents.size() + '/' + NbMaxAgents + '+' + fixedAgentIdList.size() + ' ' + now); long deadline = now - NbMaxAgents; Agent[] ag = new Agent[agents.size()]; int i = 0; for (Enumeration e = agents.elements() ; e.hasMoreElements() ;) { ag[i++] = e.nextElement(); } Agent old = agent; try { for (i--; i>=0; i--) { if ((ag[i].last <= deadline) && (!ag[i].fixed)) { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, "Agent" + ag[i].id + " [" + ag[i].name + "] garbaged"); agents.remove(ag[i].id); try { // Set current agent running in order to allow from field fixed // for sendTo during agentFinalize (We assume that only Engine // use this method). agent = ag[i]; ag[i].agentFinalize(false); agent = old; } catch (Exception exc) { logmon.log(BasicLevel.ERROR, "Agent" + ag[i].id + " [" + ag[i].name + "] error during agentFinalize", exc); } ag[i] = null; } } } finally { agent = old; } logmon.log(BasicLevel.DEBUG, getName() + ", garbage: " + agents.size()); } /** * Removes an AgentId in the fixedAgentIdList * Vector. * * @param id the AgentId of no more used fixed agent. * @throws IOException an error occurs. */ void removeFixedAgentId(AgentId id) throws IOException { fixedAgentIdList.removeElement(id); AgentServer.getTransaction().save(fixedAgentIdList, getName() + ".fixed"); } /** * Adds an AgentId in the fixedAgentIdList * Vector. * * @param id the AgentId of new fixed agent. * @throws IOException an error occurs. */ void addFixedAgentId(AgentId id) throws IOException { fixedAgentIdList.addElement(id); AgentServer.getTransaction().save(fixedAgentIdList, getName() + ".fixed"); } /** * Method used for debug and monitoring. It returns an enumeration * of all agents loaded in memory. * * @return an array containing the unique identifier of all agents in memory. */ AgentId[] getLoadedAgentIdlist() { AgentId list[] = new AgentId[agents.size()]; int i = 0; for (Enumeration e = agents.elements(); e.hasMoreElements() ;) list[i++] = e.nextElement().id; return list; } /** * Returns a string representation of the specified agent. * * @param id The string representation of the agent's unique identification. * @return A string representation of the specified agent. * @throws Exception an error occurs. * @see Engine#dumpAgent(AgentId) */ public String dumpAgent(String id) throws Exception { return dumpAgent(AgentId.fromString(id)); } /** * Returns a string representation of the specified agent. If the agent * is not present it is loaded in memory, be careful it is not initialized * (agentInitialize) nor cached in agents vector. * * @param id The agent's unique identification. * @return A string representation of specified agent. * @throws IOException an error occurs. * @throws ClassNotFoundException an error occurs. */ public String dumpAgent(AgentId id) throws IOException, ClassNotFoundException { Agent ag = (Agent) agents.get(id); if (ag == null) { ag = Agent.load(id); if (ag == null) { return id.toString() + " unknown"; } } return ag.toString(); } /** * The load method return the Agent object * designed by the AgentId parameter. If the Agent * object is not already present in the server memory, it is loaded from * the storage. * * Be careful, if the save method can be overloaded to optimize the save * process, the load procedure used by engine is always load. * * @param id The agent identification. * @return The corresponding agent. * * @exception IOException * If an I/O error occurs. * @exception ClassNotFoundException * Should never happen (the agent has already been loaded in deploy). * @exception UnknownAgentException * There is no corresponding agent on secondary storage. * @exception Exception * when executing class specific initialization */ final Agent load(AgentId id) throws IOException, ClassNotFoundException, Exception { now += 1; Agent ag = (Agent) agents.get(id); if (ag == null) { ag = reload(id); garbage(); } ag.last = now; return ag; } /** * The reload method return the Agent object * loaded from the storage. * * @param id The agent identification. * @return The corresponding agent. * * @exception IOException * when accessing the stored image * @exception ClassNotFoundException * if the stored image class may not be found * @exception Exception * unspecialized exception */ final Agent reload(AgentId id) throws IOException, ClassNotFoundException, Exception { Agent ag = null; if ((ag = Agent.load(id)) != null) { Agent old = agent; try { // Set current agent running in order to allow from field fixed // for sendTo during agentInitialize (We assume that only Engine // use this method). agent = ag; ag.agentInitialize(false); } catch (Throwable exc) { // AF: May be we have to delete the agent or not to allow // reaction on it. logmon.log(BasicLevel.ERROR, getName() + "Can't initialize Agent" + ag.id + " [" + ag.name + "]", exc); throw new Exception(getName() + "Can't initialize Agent" + ag.id); } finally { agent = old; } if (ag.logmon == null) ag.logmon = Debug.getLogger(Agent.class.getName() + ".#" + AgentServer.getServerId()); agents.put(ag.id, ag); if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + "Agent" + ag.id + " [" + ag.name + "] loaded"); } else { throw new UnknownAgentException(null, id); } return ag; } /** * Insert a message in the MessageQueue. * This method is used during initialization to restore the component * state from persistent storage. * * @param msg the message */ public void insert(Message msg) { qin.insert(msg, this); } /** * Validates all messages pushed in queue during transaction session. */ public void validate() { qin.validate(); } /** * Causes this engine to begin execution. * * @see stop */ public void start() { if (isRunning) return; thread = new EngineThread(this); int priority = AgentServer.getInteger("Engine.threadPriority", Thread.MAX_PRIORITY).intValue(); thread.setPriority(priority); thread.setDaemon(false); logmon.log(BasicLevel.DEBUG, getName() + " starting."); String rp = AgentServer.getProperty("Engine.recoveryPolicy"); if (rp != null) { for (int i = rpStrings.length; i-- > 0;) { if (rp.equals(rpStrings[i])) { recoveryPolicy = i; break; } } } isRunning = true; canStop = true; thread.start(); logmon.log(BasicLevel.DEBUG, getName() + " started."); } /** * Forces the engine to stop executing. * * @see start */ public void stop() { logmon.log(BasicLevel.DEBUG, getName() + ", stops."); isRunning = false; if (thread != null) { while (thread.isAlive()) { if (canStop) { if (thread.isAlive()) thread.interrupt(); } try { thread.join(1000L); } catch (InterruptedException exc) { continue; } } thread = null; } } /** * Get this engine's MessageQueue qin. * * @return this Engine's queue. */ public MessageQueue getQueue() { return qin; } /** * Tests if the engine is alive. * * @return true if this MessageConsumer is alive; false * otherwise. */ public boolean isRunning() { return isRunning; } /** * Saves logical clock information to persistent storage. */ public void save() throws IOException { if (modified) { stampBuf[0] = (byte)((stamp >>> 24) & 0xFF); stampBuf[1] = (byte)((stamp >>> 16) & 0xFF); stampBuf[2] = (byte)((stamp >>> 8) & 0xFF); stampBuf[3] = (byte)(stamp & 0xFF); AgentServer.getTransaction().saveByteArray(stampBuf, getName()); modified = false; } } /** * Restores logical clock information from persistent storage. */ public void restore() throws Exception { stampBuf = AgentServer.getTransaction().loadByteArray(getName()); if (stampBuf == null) { stamp = 0; stampBuf = new byte[4]; modified = true; } else { stamp = ((stampBuf[0] & 0xFF) << 24) + ((stampBuf[1] & 0xFF) << 16) + ((stampBuf[2] & 0xFF) << 8) + (stampBuf[3] & 0xFF); modified = false; } } /** * This operation always throws an IllegalStateException. */ public void delete() throws IllegalStateException { throw new IllegalStateException(); } /** * Gets the current value of stamp counter. * * @return the current value of stamp counter. */ private final int getStamp() { return stamp; } /** * Sets the stamp of the given message. * * @param msg The message to stamp. */ protected final void stamp(Message msg) { if (msg.isPersistent()) // If the message is transient there is no need to save the stamp counter. modified = true; msg.source = AgentServer.getServerId(); msg.dest = AgentServer.getServerId(); msg.stamp = ++stamp; if (stamp == Integer.MAX_VALUE) stamp = 0; } @Override public boolean isPrior(Message m1, Message m2) { if (((m1.getStamp() <= stamp) && (m2.getStamp() <= stamp)) || ((m1.getStamp() > stamp) && (m2.getStamp() > stamp))) { // The 2 messages were created in the same stamp generation, check the relative order. return (m1.getStamp() < m2.getStamp()); } else if ((m1.getStamp() > stamp) && (m2.getStamp() <= stamp)) { // The first message was created after the buffer was reset. // The second to insert was created before the buffer was reset, it is older then insert it. return true; } return false; } /** * Adds a message in "ready to deliver" list. This method allocates a * new time stamp to the message ; be Careful, changing the stamp imply * the filename change too. */ public void post(Message msg) throws Exception { stamp(msg); msg.save(); qin.push(msg); } /** * Posts a message and validates it at the same time. */ public void postAndValidate(Message msg) throws Exception { stamp(msg); msg.save(); qin.pushAndValidate(msg); } protected boolean needToBeCommited = false; protected long timeout = Long.MAX_VALUE; /** * Boolean value indicating if the agent profiling is on, by default false. * If true, the cumulative time of reaction and commit is kept for each agent. * In addition the total reaction and commit time is calculated for this engine. * This value can be adjusted through the AgentProfiling system property. */ public boolean agentProfiling = false; /** * Returns true if the agent profiling is on. * * @see fr.dyade.aaa.agent.EngineMBean#isAgentProfiling() */ public boolean isAgentProfiling() { return this.agentProfiling; } /** * Sets the agent profiling. * * @see fr.dyade.aaa.agent.EngineMBean#setAgentProfiling(boolean) */ public void setAgentProfiling(boolean agentProfiling) { this.agentProfiling = agentProfiling; } /** * Time consumed during agent's reaction. */ private long reactTime = 0L; /** * @return the reactTime */ public long getReactTime() { return reactTime; } /** * reset the reactTime */ public void resetReactTime(){ reactTime = 0; } /** * Time consumed during reaction commit. */ private long commitTime; /** * @return the commitTime */ public long getCommitTime() { return commitTime; } /** * reset the commitTime */ public void resetCommitTime(){ commitTime = 0; } /** * Reset reactTime and commitTime */ public void resetTimer(){ resetReactTime(); resetCommitTime(); } protected void onTimeOut() throws Exception {} // private static StringBuffer strbuf = new StringBuffer(); /** * Main loop of agent server Engine. */ public void run() { try { long start = 0L; long end = 0L; boolean profiling = false; averageCPUTask = new AverageCPUTask(Thread.currentThread().getId(), 12); averageCPUTask.start(AgentServer.getTimer()); main_loop: while (isRunning) { agent = null; canStop = true; // Get a notification, then execute the right reaction. try { msg = qin.get(timeout); if (msg == null) { onTimeOut(); continue; } } catch (InterruptedException exc) { continue; } canStop = false; if (! isRunning) break; // Increment workInProgress counter to indicate that the engine is working. incWorkInProgress(); if ((msg.from == null) || (msg.to == null) || (msg.not == null)) { // The notification is malformed. logmon.log(BasicLevel.ERROR, AgentServer.getName() + ": Bad message [" + msg.from + ", " + msg.to + ", " + msg.not + ']'); // Remove the failed notification .. qin.pop(); // .. then deletes it .. msg.delete(); // .. and frees it. msg.free(); continue; } CallbackNotification callbackNotification; if (msg.getNot() instanceof CallbackNotification) { callbackNotification = (CallbackNotification) msg.getNot(); } else { callbackNotification = null; } if ((msg.not.expiration <= 0L) || (msg.not.expiration >= System.currentTimeMillis())) { // The message is valid, try to load the destination agent try { agent = load(msg.to); } catch (UnknownAgentException exc) { // The destination agent don't exists, send an error // notification to sending agent. logmon.log(BasicLevel.ERROR, getName() + ": Unknown agent, " + msg.to + ".react(" + msg.from + ", " + msg.not + ")"); agent = null; if (callbackNotification != null) { callbackNotification.failed(exc); } push(AgentId.localId, msg.from, new UnknownAgent(msg.to, msg.not)); } catch (Exception exc) { // Can't load agent then send an error notification // to sending agent. logmon.log(BasicLevel.ERROR, getName() + ": Can't load agent, " + msg.to + ".react(" + msg.from + ", " + msg.not + ")", exc); agent = null; // Stop the AgentServer AgentServer.stop(false); break main_loop; } } else { if (msg.not.deadNotificationAgentId != null) { if (logmon.isLoggable(BasicLevel.DEBUG)) { logmon.log(BasicLevel.DEBUG, getName() + ": forward expired notification " + msg.from + ", " + msg.not + " to " + msg.not.deadNotificationAgentId); } ExpiredNot expiredNot = new ExpiredNot(msg.not, msg.from, msg.to); push(AgentId.localId, msg.not.deadNotificationAgentId, expiredNot); } else { if (logmon.isLoggable(BasicLevel.DEBUG)) { logmon.log(BasicLevel.DEBUG, getName() + ": removes expired notification " + msg.from + ", " + msg.not); } } } if (agent != null) { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ": " + agent + ".react(" + msg.from + ", " + msg.getStamp() + ", " + msg.not + ")"); profiling = agentProfiling || agent.agentProfiling; if (profiling) { start = System.nanoTime(); } // if (AgentServer.sdf != null) { // // SDF generation // strbuf.append("\n"); // } profiling = agentProfiling || agent.agentProfiling; if (profiling) start = System.nanoTime(); try { agent.react(msg.from, msg.not); agent.reactNb += 1; if (profiling) { end = System.nanoTime(); agent.reactTime += (end - start); reactTime += (end - start); start = end; } } catch (Exception exc) { logmon.log(BasicLevel.ERROR, getName() + ": Uncaught exception during react, " + agent + ".react(" + msg.from + ", " + msg.not + ")", exc); switch (recoveryPolicy) { case RP_EXC_NOT: default: // In case of unrecoverable error during the reaction we have // to rollback. abort(exc); if (callbackNotification != null) { callbackNotification.failed(exc); } // then continue. continue; case RP_EXIT: // Stop the AgentServer AgentServer.stop(false); break main_loop; } } } // Commit all changes then continue. commit(); if (callbackNotification != null) { callbackNotification.done(); } // // SDF generation // if (AgentServer.sdf != null) { // strbuf.append(""); // AgentServer.sdf.println(strbuf.toString()); // strbuf.setLength(0); // } // // // TODO (AF): This code could throw a NPE if the agent doesn't exist! // if (AgentServer.logsdf.isLoggable(BasicLevel.INFO)) // AgentServer.logsdf.log(BasicLevel.INFO, // "react " + agent.getAgentId() + ' ' + StringId.toStringId('N', '_', msg.getSource(), msg.getDest(), msg.getStamp())); // Agent profiling if (profiling && (agent != null)) { end = System.nanoTime(); agent.commitTime += (end - start); commitTime += (end - start); } } } catch (Throwable exc) { // There is an unrecoverable exception during the transaction, we must exit from server. logmon.log(BasicLevel.FATAL, getName() + ": Transaction problem", exc); // TODO (AF): Perhaps we should have a more restrictive action, the immediate shutdown of the transaction component for example, // and/or a brutal shutdown of the server, etc. canStop = false; // Stop the AgentServer AgentServer.stop(false); } finally { logmon.log(BasicLevel.DEBUG, getName() + ", terminates.."); terminate(); logmon.log(BasicLevel.DEBUG, getName() + " stopped."); } } /** * Commit the agent reaction in case of right termination:
    *
  • suppress the processed notification from message queue, * then deletes it ; *
  • push all new notifications in qin and qout, and saves them ; *
  • saves the agent state ; *
  • then commit the transaction to validate all changes. *
* @throws Exception an error occurs. */ void commit() throws Exception { if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ": commit()"); if (agent != null) { agent.save(); } // Check if we need a transaction or not. if (noTxIfTransient && msg.not.persistent == false && !persistentPush && !AgentServer.getTransaction().containsOperations()) { // Suppress the processed notification from message queue .. qin.pop(); // .. then deletes it .. msg.delete(); // .. and frees it. msg.free(); // Post all notifications temporary kept in mq to the right consumers, // then saves changes. Message msgToDispatch = null; while (!mq.isEmpty()) { try { msgToDispatch = (Message) mq.get(); } catch (InterruptedException exc) { continue; } if (msgToDispatch.from == null) msgToDispatch.from = AgentId.localId; MessageConsumer cons = AgentServer.getConsumer(msgToDispatch.to.getTo()); cons.postAndValidate(msgToDispatch); mq.pop(); } } else { AgentServer.getTransaction().begin(); // Suppress the processed notification from message queue .. qin.pop(); // .. then deletes it .. msg.delete(); // .. and frees it. msg.free(); // Post all notifications temporary kept in mq to the right consumers, // then saves changes. dispatch(); // Saves the agent state then commit the transaction. AgentServer.getTransaction().commit(false); // The transaction has committed, then validate all messages. Channel.validate(); AgentServer.getTransaction().release(); } persistentPush = false; if (logmon.isLoggable(BasicLevel.DEBUG)) logmon.log(BasicLevel.DEBUG, getName() + ": commited"); } /** * Abort the agent reaction in case of error during execution. In case * of unrecoverable error during the reaction we have to rollback:
    *
  • reload the previous state of agent ; *
  • remove the failed notification ; *
  • clean the Channel queue of all pushed notifications ; *
  • send an error notification to the sender ; *
  • then commit the transaction to validate all changes. *
* * @param exc the cause. * @throws Exception an error occurs. */ void abort(Exception exc) throws Exception { AgentServer.getTransaction().begin(); // Reload the state of agent. try { agent = reload(msg.to); } catch (Exception exc2) { logmon.log(BasicLevel.ERROR, getName() + ", can't reload Agent" + msg.to, exc2); throw new Exception("Can't reload Agent" + msg.to); } // Remove the failed notification .. qin.pop(); // .. then deletes it .. msg.delete(); // .. and frees it. msg.free(); // Clean the Channel queue of all pushed notifications. clean(); // Send an error notification to client agent. push(AgentId.localId, msg.from, new ExceptionNotification(msg.to, msg.not, exc)); dispatch(); AgentServer.getTransaction().commit(false); // The transaction has committed, then validate all messages. Channel.validate(); AgentServer.getTransaction().release(); } /** the tick counter that reflects activity in engine. */ long workInProgress = 0L; /** * Returns the tick counter that reflects activity in engine. * @return the tick counter that reflects activity in engine. */ @Override public final long getWorkInProgress() { return workInProgress; } /** * Increments the tick counter that reflects activity in engine. * @see #workInProgress */ @Override public final void incWorkInProgress() { workInProgress++; } /** The average use of CPU by the Engine thread during last minute. */ AverageCPUTask averageCPUTask = null; /** * Returns the average use of CPU by the Engine thread during last minute. * * @return the average use of CPU by the Engine thread during last minute. */ @Override public final int getAverageCPU() { if (averageCPUTask != null) return averageCPUTask.getAverage(); return 0; } EngineAverageLoadTask averageLoadTask = null; public final void resetAverageLoad() { if (averageLoadTask != null) averageLoadTask.reset(); } /** * Returns the load averages for the last minute. * @return the load averages for the last minute. */ @Override public final float getAverageLoad1() { return averageLoadTask.getAverageLoad1(); } /** * Returns the load averages for the past 5 minutes. * @return the load averages for the past 5 minutes. */ @Override public final float getAverageLoad5() { return averageLoadTask.getAverageLoad5(); } /** * Returns the load averages for the past 15 minutes. * @return the load averages for the past 15 minutes. */ @Override public final float getAverageLoad15() { return averageLoadTask.getAverageLoad15(); } class EngineAverageLoadTask extends AverageLoadTask { public EngineAverageLoadTask(Timer timer) { start(timer); } /** * Returns the number of waiting messages in the engine. * * @see fr.dyade.aaa.common.AverageLoadTask#countActiveTasks() */ @Override protected long countActiveTasks() { return AgentServer.engine.getNbWaitingMessages(); } } /** * Returns a report about the distribution of messages type in queue. */ public String report() { return qin.report(); } /** * Returns a string representation of this engine. * * @return A string representation of this engine. */ public String toString() { StringBuffer strbuf = new StringBuffer(); strbuf.append('(').append(super.toString()); strbuf.append(",name=").append(getName()); strbuf.append(",running=").append(isRunning()); strbuf.append(",agent=").append(agent).append(')'); return strbuf.toString(); } /** * Checks if the current thread calling this method * belongs to the engine. * * @return true if the current thread calling this method * belongs to the engine */ public boolean isEngineThread() { return Thread.currentThread() == thread; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy