JASocket contains a number of commands, some are for there as an aid in managing the cluster and others are there to illustrate how they work. Here we look at the implementation of some of those commands to aid you in the implementation of your own.

ToAgent

The toAgent command is of particular interest as it is used to send commands to other nodes. The arg string consists of the node address (or resource name), the name of another command and [optionally] the arg string of that other command. This command removes the address from its arg string and creates an EvalAgent initialized with the remainder of the arg string. The EvalAgent is then shipped to the designated node.

The latencyTest command measures the time it takes to send a KeepAliveAgent to another node and get a response. This command has an optional argument--the number of times the request/response is to be performed.

The who command lists every operator logged in on any node in the cluster. The display includes the operator name, node where the operator is logged in, how long the operator has been logged in, the number of commands entered and how long it has been since the last command.

Message passing between threads and callbacks can both make exception handling more difficult. On the other hand, the use of 2-way messages (request/response) provides us with a natural default: uncaught exceptions should be passed back to the requesting actor for handling. And that is exactly what JActor does.

JActor supports an exception handler for each request received by an actor, though exception handlers should only be used in asynchronous actor methods, e.g. methods with an RP continuation parameter.

The JLPCActor class provides two methods for working with exceptions: void setExceptionHandler(ExceptionHandler) and ExceptionHandler getExceptionHandler(), where the value null is used to indicate that there is no excepton handler and the default should be used.

If an exception occurs when an exception handler is processing an exception, the new exception is passed back to the actor which sent the current request for handling.

And when a request is sent as an event (Request.sendEvent) and the target actor does not handle an exception, the stack trace of the exception is printed. The printing is done in the JAMailboxFactory.eventException method, which can easily be overridden if a logger is to be used instead of printing.

(The following example presumes that you are familiar with the earlier blog post, JActor API Basics.)

import org.agilewiki.jactor.*;

import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {

public void start(final RP rp) throws Exception {

ThrowsException throwsException = new ThrowsException();

throwsException.initialize(getMailboxFactory().createMailbox());

setExceptionHandler(new ExceptionHandler() {

public void process(Exception exception)

throws Exception {

System.out.println(exception.getMessage());

rp.processResponse(null);

}

});

ThrowException.req.send(this, throwsException, new RP() {

public void processResponse(Object response)

throws Exception {

System.out.println("No exception");

rp.processResponse(null);

}

});

}

}

The Test actor now creates and initializes the ThrowsException actor. Test also defines an exception handler for the current (Start) request and then sends a ThrowException request to the ThrowsException actor.

import org.agilewiki.jactor.*;

import org.agilewiki.jactor.lpc.*;

public class ThrowException

extends Request<Object, ThrowsException> {

public static final ThrowException req =

new ThrowException();

public void processRequest(JLPCActor targetActor, RP rp)

throws Exception {

ThrowsException a = (ThrowsException) targetActor;

a.throwException(rp);

}

public boolean isTargetType(Actor targetActor) {

return targetActor instanceof ThrowsException;

}

}

The ThrowException request is a singleton that calls the asynchronous method ThrowsException.throwException(RP).

import org.agilewiki.jactor.*;

import org.agilewiki.jactor.lpc.*;

public class ThrowsException extends JLPCActor {

public void throwException(RP rp) throws Exception {

throw new Exception("Boo!");

}

}

The ThrowsException actor has a single asynchronous method for throwing an exception.

Vertical scaling is today a major issue when writing server code. Threads and locks are the traditional approach to making full utilization of fat (multi-core) computers, but result is code that is difficult to maintain and which to often does not run much faster than single-threaded code.

Actors make good use of fat computers but tend to be slow as messages are passed between threads. Attempts to optimize actor-based programs results in actors with multiple concerns (loss of modularity) and lots of spaghetti code.

The approach used by JActor is to minimize the messages passed between threads by executing the messages sent to idle actors on the same thread used by the actor which sent the message. Message buffering is used when messages must be sent between threads, and two-way messaging is used for implicit flow control. The result is an approach that is easy to maintain and which, with a bit of care to the architecture, provides extremely high rates of throughput.

On an intel i7, 250 million messages can be passed between actors in the same JVM per second–several orders of magnitude faster than comparable actor frameworks.

A fully durable in-memory database was also developed as a proof of concept, which processes transactions at a rate of 1 million per second when using an SSD for the backing store. Again, this is orders of magnitude greater than comparable approaches.

Actor-based programming is quite appealing and holds a lot of potential.

Being message-based, it looks like the perfect fit for distributed, horizontally scalable applications.

With actor methods assured of thread safety while operating in a multi-threading environment, actor-based programming looks like it could become the ideal light-weight alternative to EJB.

Vertical scaling comes naturally when using actors, and should be much more maintainable than the equivalent code written with threads and locks.

Developers new to actor-based programming quickly become enthralled. It is event-base programming, but with thread safety and modularity. The code is clean and easy to write--as long as the actors are kept small and focused on a single concern. But trouble starts when you start dealing with performance issues.

You try writing a simple benchmark and, as soon as you crank up the numbers you get an out of memory exception. Oh! Actors use one-way messages and there is no flow control. So you add ACK messages to your actors. Suddenly everything is just a bit less clear and application code is a bit more difficult to write.

Now you have your benchmarks working and look at the numbers. Disaster! Actors pass all messages between threads, and that introduces a huge amount of latency. You ask some developers who have more experience with actor-based programming and you are told that you are using actors the wrong way.

A well designed actor-based application minimizes the number of messages that are passed between actors, at least where performance is important. So you stop using small actors which address only a single concern. You loose modularity and clearity. And your actors become big bowls of spaghetti. Actor-based programming becomes ever so much less interesting.

On the other hand, we can change the way actors work, and realize the real potential of actor-based programming. We can introduce 2-way messaging through the use of continuations. And we can minimize the passing of messages between threads. Then we can use small, tightly focused actors with clean/readable code without having to give up performance.