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

org.artifact.actor.DefaultActorManager Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
package org.artifact.actor;

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 cn.hutool.core.collection.CollUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

/**
 * Default ActorManager implementation. 
 * 
 * @author BFEIGENB
 *
 */
public class DefaultActorManager implements ActorManager {

	static final Log log = LogFactory.get(DefaultActorManager.class);
	
	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 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) {
					log.warn("DefaultActorManager: no configutration: {}" ,e);
				}
			}
			if (!CollUtil.isEmpty(p)) {
				options = new HashMap();
				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 actors = new LinkedHashMap();

	protected Map runnables = new LinkedHashMap();

	protected Map waiters = new LinkedHashMap();

	/**
	 * 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 xkeys = new HashSet();
		xkeys.addAll(actors.keySet());
		Iterator 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 zactors = new ArrayList(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> sentMessages = new HashMap>();

	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 res = null;
		synchronized (sentMessages) {
			List l = sentMessages.get(actor.getName());
			if (!CollUtil.isEmpty(l)) {
				res = new LinkedList();
				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 l = sentMessages.get(aname);
							if (l == null) {
								l = new LinkedList();
								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 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 xactors = cloneActors();
		List catMembers = new LinkedList();
		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 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 getCategories() {
		Map xactors = cloneActors();
		Set res = new TreeSet();
		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 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 cloneActors() {
		Map xactors;
		synchronized (actors) {
			xactors = new HashMap(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 trunnables = new HashMap();

	/**
	 * 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 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 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 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) {
					log.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 threads = new LinkedList();

	/**
	 * 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() {
		log.trace("terminateAndWait waiting on termination of {} 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 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 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 clazz, String name, Map 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 clazz, String name, Map 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy