package com.ibm.jactors; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.TreeSet; import com.ibm.jactors.utils.Utils; /** * Default ActorManager implementation. * * @author BFEIGENB * */ public class DefaultActorManager extends Utils implements ActorManager { public static final int DEFAULT_ACTOR_THREAD_COUNT = 10; protected static DefaultActorManager instance; /** * Get the default instance. Uses ActorManager.properties for configuration. * * @return shared instance */ public static DefaultActorManager getDefaultInstance() { if (instance == null) { instance = new DefaultActorManager(); Map<String, Object> options = null; // ConfigUtils configUtils = new ConfigUtils(); // Properties p = configUtils // .loadProperties("ActorManager.properties"); Properties p = new Properties(); try { p.load(new FileInputStream("ActorManager.properties")); } catch (IOException e) { try { p.load(new FileInputStream("/resource/ActorManager.properties")); } catch (IOException e1) { logger.warning("DefaultActorManager: no configutration: " + e); } } if (!isEmpty(p)) { options = new HashMap<String, Object>(); for (Object key : p.keySet()) { String skey = (String) key; options.put(skey, p.getProperty(skey)); } } instance.initialize(options); } return instance; } /** Configuration key for thread count. */ public static final String ACTOR_THREAD_COUNT = "threadCount"; protected Map<String, AbstractActor> actors = new LinkedHashMap<String, AbstractActor>(); protected Map<String, AbstractActor> runnables = new LinkedHashMap<String, AbstractActor>(); protected Map<String, AbstractActor> waiters = new LinkedHashMap<String, AbstractActor>(); /** * Detach an actor. */ @Override public void detachActor(Actor actor) { if (((AbstractActor) actor).getManager() != this) { throw new IllegalStateException("actor not owned by this manager"); } String name = actor.getName(); synchronized (actors) { if (actors.containsKey(name)) { ((AbstractActor) actor).setManager(null); actors.remove(name); runnables.remove(name); waiters.remove(name); } else { actor = null; } } if (actor != null) { actor.deactivate(); } } /** * Detach all actors. */ public void detachAllActors() { Set<String> xkeys = new HashSet<String>(); xkeys.addAll(actors.keySet()); Iterator<String> i = xkeys.iterator(); while (i.hasNext()) { detachActor(actors.get(i.next())); } synchronized (actors) { actors.clear(); runnables.clear(); waiters.clear(); } } protected Random rand = new Random(); /** * Create a list of actors in a pseudo-random order. * */ public void randomizeActors() { synchronized (actors) { AbstractActor[] xactors = getActors(); List<AbstractActor> zactors = new ArrayList<AbstractActor>(xactors.length); for (AbstractActor a : xactors) { zactors.add(rand.nextInt(zactors.size() + 1), a); } actors.clear(); for (AbstractActor a : zactors) { actors.put(a.getName(), a); } } } /** * Count the number of actors of a given type. * * @param type the class to count (also its subclasses) */ @Override public int getActorCount(Class type) { int res = 0; if (type != null) { synchronized (actors) { for (String key : actors.keySet()) { Actor a = actors.get(key); if (type.isAssignableFrom(a.getClass())) { res++; } } } } else { synchronized (actors) { res = actors.size(); } } return res; } /** * Get actors managed by this manager. * * @return actors */ public AbstractActor[] getActors() { AbstractActor[] res = new AbstractActor[actors.size()]; copyMembers(res); return res; } protected void copyMembers(AbstractActor[] res) { int count = 0; synchronized (actors) { for (String key : actors.keySet()) { res[count++] = actors.get(key); } } } protected Map<String, List<Message>> sentMessages = new HashMap<String, List<Message>>(); protected boolean recordSentMessages = true; public boolean getRecordSentMessages() { return recordSentMessages; } public void setRecordSentMessages(boolean recordSentMessages) { this.recordSentMessages = recordSentMessages; } /** * Get a list of pending messages and then clear it. * * @param actor * receiving actor * @return */ public Message[] getAndClearSentMessages(Actor actor) { List<Message> res = null; synchronized (sentMessages) { List<Message> l = sentMessages.get(actor.getName()); if (!isEmpty(l)) { res = new LinkedList<Message>(); res.addAll(l); l.clear(); } } return res != null ? res.toArray(new Message[res.size()]) : null; } volatile protected long lastSendTime, lastDispatchTime; public long getLastSendTime() { return lastSendTime; } public long getLastDispatchTime() { return lastDispatchTime; } volatile protected int sendCount, lastSendCount; volatile protected int dispatchCount, lastDispatchCount; /** Get most recent sends/second count. */ public int getSendPerSecondCount() { return lastSendCount; } /** Get most recent thread dispatches/second count. */ public int getDispatchPerSecondCount() { synchronized (actors) { return lastDispatchCount; } } protected void incDispatchCount() { synchronized (actors) { dispatchCount += 1; lastDispatchTime = new Date().getTime(); // logger.info("incDispatchCount: dc=%d", dispatchCount); } } protected void clearDispatchCount() { synchronized (actors) { dispatchCount = 0; lastDispatchCount = 0; // logger.info("clearDispatchCount: dc=%d, ldc=%d", dispatchCount, // lastDispatchCount); } } protected void updateLastDispatchCount() { synchronized (actors) { lastDispatchCount = dispatchCount; dispatchCount = 0; // logger.info("updateLastDispatchCount: dc=%d, ldc=%d", // dispatchCount, lastDispatchCount); } } /** * Send a message. * * @param message * message to * @param from * source actor * @param to * target actor * @return number of receiving actors */ @Override public int send(Message message, Actor from, Actor to) { int count = 0; if (message != null) { AbstractActor aa = (AbstractActor) to; if (aa != null) { if (!aa.isShutdown() && !aa.isSuspended() && aa.willReceive(message.getSubject())) { DefaultMessage xmessage = (DefaultMessage) ((DefaultMessage) message).assignSender(from); // logger.trace(" %s to %s", xmessage, to); aa.addMessage(xmessage); xmessage.fireMessageListeners(new MessageEvent(aa, xmessage, MessageEvent.MessageStatus.SENT)); sendCount++; lastSendTime = new Date().getTime(); if (recordSentMessages) { synchronized (sentMessages) { String aname = aa.getName(); List<Message> l = sentMessages.get(aname); if (l == null) { l = new LinkedList<Message>(); sentMessages.put(aname, l); } // keep from getting too big if (l.size() < 100) { l.add(xmessage); } } } count++; synchronized (actors) { actors.notifyAll(); } } } } return count; } /** * Send a message. * * @param message * message to * @param from * source actor * @param to * target actors * @return number of receiving actors */ @Override public int send(Message message, Actor from, Actor[] to) { int count = 0; for (Actor a : to) { count += send(message, from, a); } return count; } /** * Send a message. * * @param message * message to * @param from * source actor * @param to * target actors * @return number of receiving actors */ @Override public int send(Message message, Actor from, Collection<Actor> to) { int count = 0; for (Actor a : to) { count += send(message, from, a); } return count; } /** * Send a message. * * @param message * message to * @param from * source actor * @param category * target actor category * @return number of receiving actors */ @Override public int send(Message message, Actor from, String category) { int count = 0; Map<String, Actor> xactors = cloneActors(); List<Actor> catMembers = new LinkedList<Actor>(); for (String key : xactors.keySet()) { Actor to = xactors.get(key); if (category.equals(to.getCategory()) && (to.getMessageCount() < to.getMaxMessageCount())) { catMembers.add(to); } } // find an actor with lowest message count int min = Integer.MAX_VALUE; Actor amin = null; for (Actor a : catMembers) { int mcount = a.getMessageCount(); if (mcount < min) { min = mcount; amin = a; } } if (amin != null) { count += send(message, from, amin); // } else { // throw new // IllegalStateException("no capable actors for category: " + // category); } return count; } /** * Send a message to all actors. * * @param message * message to * @param from * source actor * @return number of receiving actors */ @Override public int broadcast(Message message, Actor from) { int count = 0; Map<String, Actor> xactors = cloneActors(); for (String key : xactors.keySet()) { Actor to = xactors.get(key); count += send(message, from, to); } return count; } /** * Get the current categories. * * @return categories */ @Override public Set<String> getCategories() { Map<String, Actor> xactors = cloneActors(); Set<String> res = new TreeSet<String>(); for (String key : xactors.keySet()) { Actor a = xactors.get(key); res.add(a.getCategory()); } return res; } /** * Get the number of actors in a category. * * @param name * @return */ public int getCategorySize(String name) { Map<String, Actor> xactors = cloneActors(); int res = 0; for (String key : xactors.keySet()) { Actor a = xactors.get(key); if (a.getCategory().equals(name)) { res ++; } } return res; } protected Map<String, Actor> cloneActors() { Map<String, Actor> xactors; synchronized (actors) { xactors = new HashMap<String, Actor>(actors); } return xactors; } /** * Suspend an actor until it has a read message. * * @param actor * receiving actor */ public void awaitMessage(AbstractActor actor) { synchronized (actors) { waiters.put(actor.getName(), actor); // actors.notifyAll(); // logger.trace("awaitMessage waiters=%d: %s",waiters.size(), a); } } protected Map<String, ActorRunnable> trunnables = new HashMap<String, ActorRunnable>(); /** * Get the Runnable by name. * * @param name * thread name * @return runnable */ public ActorRunnable getRunnable(String name) { return trunnables.get(name); } /** * Get the number of busy runnables (equivalent to threads). * @return */ public int getActiveRunnableCount() { int res = 0; synchronized (actors) { for (String key : trunnables.keySet()) { if (trunnables.get(key).hasThread) { res++; } } } return res; } /** * Add a dynamic thread. * * @param name * @return */ public Thread addThread(String name) { Thread t = null; synchronized (actors) { if (trunnables.containsKey(name)) { throw new IllegalStateException("already exists: " + name); } ActorRunnable r = new ActorRunnable(); trunnables.put(name, r); t = new Thread(threadGroup, r, name); threads.add(t); //System.out.printf("addThread: %s", name); } t.setDaemon(true); t.setPriority(getThreadPriority()); return t; } /** * Remove a dynamic thread. * * @param name */ public void removeThread(String name) { synchronized (actors) { if (!trunnables.containsKey(name)) { throw new IllegalStateException("not running: " + name); } //System.out.printf("removeThread: %s", name); trunnables.remove(name); Iterator<Thread> i = threads.iterator(); while(i.hasNext() ) { Thread xt = i.next(); if(xt.getName().equals(name)) { i.remove(); xt.interrupt(); break; } } } } protected ThreadGroup threadGroup; public ThreadGroup getThreadGroup() { return threadGroup; } protected void createThread(int i) { addThread("actor" + i); } /** * Initialize this manager. Call only once. */ @Override public void initialize() { initialize(null); } private boolean initialized; /** * Initialize this manager. Call only once. * * @param options * map of options */ @Override public void initialize(Map<String, Object> options) { if (!initialized) { initialized = true; int count = getThreadCount(options); ThreadGroup tg = new ThreadGroup("ActorManager" + groupCount++); threadGroup = tg; for (int i = 0; i < count; i++) { createThread(i); } running = true; for (Thread t : threads) { // logger.trace("procesNextActor starting %s", t); t.start(); } Thread Counter = new Thread(new Runnable() { @Override public void run() { while (running) { try { trendValue = sendCount - dispatchCount; // logger.trace("Counter thread: sc=%d, dc=%d, t=%d", // sendCount, dispatchCount, trendValue); lastSendCount = sendCount; sendCount = 0; updateLastDispatchCount(); Thread.sleep(1000); } catch (InterruptedException e) { break; } } sendCount = lastSendCount = 0; clearDispatchCount(); } }); Counter.setDaemon(true); lastDispatchTime = lastSendTime = new Date().getTime(); Counter.start(); } } /** * Get the thread priority to use. Default is 1 less than current. * * @return priority value */ public int getThreadPriority() { return Math.max(Thread.MIN_PRIORITY, Thread.currentThread().getPriority() - 1); } protected int getThreadCount(Map<String, Object> options) { Integer count = null; Object xcount = options != null ? options.get(ACTOR_THREAD_COUNT) : null; if (xcount != null) { if (xcount instanceof Integer) { count = (Integer) xcount; } else { count = Integer.parseInt((String) xcount.toString()); } } if (count == null) { count = DEFAULT_ACTOR_THREAD_COUNT; } return count; } /** public intended only for "friend" access. */ public class ActorRunnable implements Runnable { public boolean hasThread; public AbstractActor actor; public void run() { // logger.trace("procesNextActor starting"); int delay = 1; while (running) { try { if (!procesNextActor()) { // logger.trace("procesNextActor waiting on actor"); // sleep(delay * 1000); synchronized (actors) { // TOOD: adjust this delay; possible parameter // we want to minizmize overhead (make bigger); // but it has a big impact on message processing // rate (makesmaller) // actors.wait(delay * 1000); actors.wait(100); } delay = Math.max(5, delay + 1); } else { delay = 1; } } catch (InterruptedException e) { } catch (Exception e) { logger.error("procesNextActor exception", e); } } // logger.trace("procesNextActor ended"); } protected boolean procesNextActor() { boolean run = false, wait = false, res = false; actor = null; synchronized (actors) { for (String key : runnables.keySet()) { actor = runnables.remove(key); break; } } if (actor != null) { // first run never started run = true; actor.setHasThread(true); hasThread = true; try { actor.run(); } finally { actor.setHasThread(false); hasThread = false; } } else { synchronized (actors) { for (String key : waiters.keySet()) { actor = waiters.remove(key); break; } } if (actor != null) { // then waiting for responses wait = true; actor.setHasThread(true); hasThread = true; try { res = actor.receive(); if (res) { incDispatchCount(); } } finally { actor.setHasThread(false); hasThread = false; } } } // if (!(!run && wait && !res) && a != null) { // logger.trace("procesNextActor %b/%b/%b: %s", run, wait, res, a); // } return run || res; } } protected static int groupCount; protected List<Thread> threads = new LinkedList<Thread>(); /** * Get the actor threads. * * @return */ public Thread[] getThreads() { return threads.toArray(new Thread[threads.size()]); } /** * Terminate processing and wait for all threads to stop. */ @Override public void terminateAndWait() { logger.trace("terminateAndWait waiting on termination of %d threads", threads.size()); terminate(); waitForThreads(); } /** * Wait for all threads to stop. Must have issued terminate. */ public void waitForThreads() { if (!terminated) { throw new IllegalStateException("not terminated"); } for (Thread t : threads) { try { // logger.info("terminateAndWait waiting for %s...", t); t.join(); } catch (InterruptedException e) { // logger.info("terminateAndWait interrupt"); } } } boolean running, terminated; /** * Terminate processing. */ @Override public void terminate() { terminated = true; running = false; for (Thread t : threads) { t.interrupt(); } synchronized (actors) { for (String key : actors.keySet()) { actors.get(key).deactivate(); } } sentMessages.clear(); sendCount = lastSendCount = 0; clearDispatchCount(); } /** * Create an actor and associate it with this manager. * * @param clazz * the actor class * @param the * actor name; must be unique */ @Override public Actor createActor(Class<? extends Actor> clazz, String name) { return createActor(clazz, name, null); } /** * Create an actor and associate it with this manager then start it * * @param clazz * the actor class * @param the * actor name; must be unique */ @Override public Actor createAndStartActor(Class<? extends Actor> clazz, String name) { return createAndStartActor(clazz, name, null); } /** * Create an actor and associate it with this manager then start it. * * @param clazz * the actor class * @param the * actor name; must be unique * @param options * actor options */ @Override public Actor createAndStartActor(Class<? extends Actor> clazz, String name, Map<String, Object> options) { Actor res = createActor(clazz, name, options); startActor(res); return res; } /** * Create an actor and associate it with this manager. * * @param clazz * the actor class * @param the * actor name; must be unique * @param options * actor options */ @Override public Actor createActor(Class<? extends Actor> clazz, String name, Map<String, Object> options) { AbstractActor a = null; synchronized (actors) { if (!actors.containsKey(name)) { try { a = (AbstractActor) clazz.newInstance(); a.setName(name); a.setManager(this); } catch (Exception e) { throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException( "mapped exception: " + e, e); } } else { throw new IllegalArgumentException("name already in use: " + name); } } return a; } /** * Start an actor. Must have been created by this manager. * * @param actor * the actor */ @Override public void startActor(Actor actor) { if (((AbstractActor) actor).getManager() != this) { throw new IllegalStateException("actor not owned by this manager"); } String name = actor.getName(); synchronized (actors) { if (actors.containsKey(name)) { throw new IllegalStateException("already started"); } ((AbstractActor) actor).shutdown = false; actors.put(name, (AbstractActor) actor); runnables.put(name, (AbstractActor) actor); } actor.activate(); } protected int trendValue = 0, maxTrendValue = 10; public int getTrendValue() { return trendValue; } public void setTrendValue(int trendValue) { this.trendValue = trendValue; } public int getMaxTrendValue() { return maxTrendValue; } public void setMaxTrendValue(int maxTrendValue) { this.maxTrendValue = maxTrendValue; } }