JEgg Developer's Guide
NOTE: This guide currently refers to JEgg versions prior to
0.3.0. It is being re-written for 0.3.0.
JEgg development should be simple - if it becomes too complicated, it's
defeating its purpose, to simplify the development of multithreaded
apps in Java and increase their robustness. To that end, this
guide will be short and sweet.
Introduction
JEgg aims to provide a lightweight framework for implementing "eggs",
which are Java objects that communicate with one another by sending
messages, which are just ordinary Java objects. These objects are
called "eggs" because their external aspect, or public interface, is
relatively featureless, hiding the internal details (like an egg!).
When a message is sent to an egg, the framework queues the message, by
priority, for later delivery to the egg. Subsequently, the JEgg
message dispatcher assigned to the egg takes the message off the egg's
message queue and passes it as an argument to the appropriate handler
method on the egg. The egg's handler method, implemented by you,
the application developer, acts on the message in whatever way the
application logic requires.
The net effect of the message dispatch mechanism is:
- Each Egg appears to execute completely independently.
- The Egg's thread safety has become a nonissue.
Synchronization is only necessary when accessing a data object that has
been purposely shared with another Egg.
- Race conditions between any two eggs can't occur.
Race conditions between three or more Eggs can occur, but they are
completely explicit, not hidden in the subtleties of program
code. A common race condition that can occur is the lookup by one
Egg of another Egg's message port using the PortRegistry.
However, the race condition will manifests itself in the worst case by
a lookup failure message delivered to the requesting Egg.
The JEgg Base Class
All Eggs must extend the framework class jegg.Egg.
The minimal requirements for an Egg implementation are the following:
- It must at least define the following public method:
public void handle
(Object message)
That's it. The handle(Object)
method is where your Egg decides what to do with messages it doesn't
recognize. If you extend jegg.Egg and
have only a single handle(Object) method,
your Egg will be compatible with the JEgg framework, but it obviously
won't be very useful.
To implement the application logic that your new Egg is intended for,
add additional handle(..)
method, with public
visibility, to your derived class. Each handle method
should take a single argument, which declares the type of the message
that the handle
method will recognize. For instance:
public class MyEgg
extends jegg.Egg {
public MyEgg (Object id) {super(id);} // more on this later
public MyEgg (Object id, Dispatcher d) {super(id,d);} // more on this
later
public void handle (Object o) { /* 'default' handler */ }
public void handle (Bacon strip) { ... }
public void handle (Toast slice) { ... }
public void handle (Coffee cup) { ... }
}
A simple (and obvious) way to view an Egg is as a Finite State Machine
(FSM) with a single state. The messages delivered to the Egg
consitute events that trigger self-transitions (transitions from the
state back to itself), and the handle methods
are the transitions.
The Egg base class has two constructors, at least one of which your egg
will explicitly invoke:
- Egg
(Object id)
- Egg
(Object id, Dispatcher d)
The first constructor assigns the argument as an identifier for the egg
and assigns the egg to the default message dispatcher. The second
form of the constructor assigns the egg to the passed-in
dispatcher.
Any other methods in your derived Egg should be directly related to its
implementation with visibility no higher than protected.
The JEgg Message Dispatcher
The JEgg framework message dispatcher is implemented by the jegg.Dispatcher
class. The message dispatcher delivers incoming messages to
each of the eggs assigned to it, round-robin style. Each message
is allowed be handled to completion by the egg it was delivered to
before the dispatcher delivers the next message to the next egg.
Messages can be assigned different priorities, although in the current
version, priority is only taken into account between messages in the
same queue (egg).
The framework automatically creates a default dispatcher that an egg is
assigned to if it is not explicitly constructed with a dispatcher.
You should use the default dispatcher as much as possible, especially
for eggs that spend most of their time doing very little.
However, you may need to partition your eggs among different
dispatchers, either because you have too many to assign to the default
dispatcher or because some of them may block the dispatcher for long
intervals as they handle messages. Actually, any egg that can
potentially take a long time to process a message should probably be
assigned to its own individual dispatcher, unless the other eggs
assigned to that dispatcher are tolerant.
How many Eggs are too many to assign to a single dispatcher? I
don't know, but a future version of the dispatcher will track its
utilization and emit runtime warnings if it appears to be over
committed. A well designed application will allow its eggs to be
assigned to dispatchers through the application configuration., which
will help you to tweak the application performance without rewriting
any code.
The Egg Port
Each egg has a single message port that can be used by other eggs to
send messages to it. An egg's message port can be obtained by
calling the following method (one of the few public methods on the egg):
jegg.Port
jegg.Egg.getPort(void)
The Port class has a method for sending a message to the Egg it is
associated with:
void
jegg.Port.send(jegg.Message msg)
The send method takes an object of type jegg.Message
which encloses the actual object to be delivered to the target
egg. The object to send to the target should be wrapped in a jegg.Message
object using the jegg.Egg.createMessage(Object)
convenience method from the Egg base class:
// In an egg
method ...
Object msg = new SomeApplicationSpecificMessageClass();
Message
eggMessage = createMessage(msg);
Port toEgg =
targetEgg.getPort();
toEgg.send(eggMessage);
Often, an egg will need to communicate with another egg that it doesn't
have a reference to so that it can't directly retrieve the target egg's
message port. If the target egg has published its message port in
the
JEgg framework port registry, under a well-known name, then any other
egg can lookup the port in the registry and use it to send messages to
the target egg. The JEgg port registry is described next.
The Port Registry
The Egg base class provides another convenience method that the derived
egg can use to publish its message port in the JEgg framework port
registry:
void
jegg.Egg.publishPort (String name);
This method will register the egg's message port in the port registry
under the specified name. Often, an egg will invoke this method
in its constructor.
An egg can also use a Egg base class method to lookup the port of
another egg published in the port registry:
void
jegg.Egg.requestPort (String name);
If a port is registered in the port registry under the
specified name, then the port will be sent to the requesting egg
wrapped in a jegg.registry.LocatePortResponse
message. For instance:
public MyEgg
extends Egg {
public MyEgg() {
super(MyEgg.class.getName());
requestPort("some-egg-id");
}
public void handle(LocatePortResponse r) {
Port p = r.getPort();
p.send(createMessage("SPAM!"));
}
}
If the target egg hasn't yet published it's port when the port registry
receives the lookup request, then the request is saved until a
port is published under a name matching the name specified in the
request.
Sending Messages
Previous examples have already shown how to send simple point-to-point
messages at the "default" priority. This section describes how to
"broadcast" messages and send messages with higher or lower priorities.
Message Broadcast
Some eggs will be designed to emit messages for delivery to all
interested eggs. JEgg accomodates this by allowing an egg to
"bind" to the port of another egg in such a way that any message
broadcast by the latter will be delivered to all eggs that have
explicitly bound to that port.
An egg can bind to the port of another egg using the base class method jegg.Egg.bindToPort(jegg.Port).
For instance,
public MyEgg
extends Egg {
public MyEgg() {
super(MyEgg.class.getName() /*why not..*/);
requestPort("emitter-egg");
}
public void handle(LocatePortResponse r) {
Port p = r.getPort();
bindToPort(p);
}
public void handle(SomeBroadcastMessage m) {
// Handle message broadcast from other egg.
}
}
In the preceding, the SomeBroadcastMessage was broadcast by the egg
that the MyEgg instance had bound to. How did that egg broadcast
the message?
public EmitterEgg
extends Egg {
public EmitterEgg() {
super(EmitterEgg.class.getName());
publishPort("emitter-egg");
}
public void handle(SomeTriggerEvent e) {
// This event (message) causes me to
// emit another message
send(new SomeBroadcastMessage());
}
}
Notice that the broadcasting egg used another base class method:
void
jegg.Egg.send(Object)
Notice also that the argument to this method doesn't have to be an
instance of jegg.Message.
You just pass the "raw" message object that you want to be delivered to
any listener eggs.
Responding to Messages
Often, the implementation of a handle()
method will require the ability to send a response message back to the
egg that sent the message being handled at that instant. The Egg
base class offers convenience methods to messages to be responded to as
they are being handled, at a later time, and with varying
priorities.
Responding to the current message
To respond to the message being currently handled invoke:
void
jegg.Egg.respond (Object)
When invoked during the handling of a message from another egg, either
point-to-point or broadcast, this method will send the object back to
the egg that sent the current message. Notice that the response
object doesn't have to be wrapped in a jegg.Message
instance; consequently, the messages is sent at the default
priority.
For instance:
public MyEgg
extends Egg {
public MyEgg() {
super(MyEgg.class.getName() /*why not..*/);
requestPort("emitter-egg");
}
public void handle(LocatePortResponse r) {
Port p = r.getPort();
bindToPort(p);
}
public void handle(SomeBroadcastMessage m) {
respond ("Message received - over and out!");
}
}
To send at a different priority, use the other form of the jegg.Egg.respond
method that also takes a jegg.Priority
argument in addition to the response object.
Responding to a message later
During the handling of a message, an egg may determine that it should
respond, but can't immediately. In this situation, the handler
can defer the response (during the handling of a different message,
say) by saving the port of the sending egg and/or the current message
being handled. The base class methods that can be used for this
are:
jegg.Port
jegg.Egg.getFromPort()
jegg.Message
jegg.Egg.getCurrentMessage()
Both of these message can only be called during the handling of a
message, which is pretty much any time, right? (Since the egg only
executes when its handling a message). To simplify matters, the jegg.Message
class provides access to the port of the sending egg, so if you need
both port and message, you only need to save the return value of the getCurrentMessage()
method.
To respond to a message that was previously delivered and saved (as
just described), use the form of the response methd that allows
explicit specification of the target egg's port:
void
jegg.Egg.respond (jegg.Port, Object)
This method is really no different from using the jegg.Port.send
method directly except that the respond method
will take care of wrapping the response object in a jegg.Message
instance before writing it to the port.
Timers
Since eggs communicate asynchronously, it's important to be able to
detect when an important message is late. A timer that can
deliver a timeout message to an egg fits in well with the JEgg
messaging scheme.
The jegg.Egg
base class offers two types of timers, repeating and single-shot:
jegg.timer.Timer
createRepeatingTimer(long interval, long delay)
jegg.timer.Timer
createSingleShotTimer(long delay)
A repeating timer will continue to deliver jegg.timer.Timeout messages
to the egg until it's cancelled by invoking jegg.timer.Timer.cancel().
A single-shot timer is automatically cancelled after it delivers its
single timeout message, unless it's cancelled by the egg before it
expires.
Using single-shot timers
Single-shot timers are useful for putting an upper limit on an
anticipated message that must be received before further action can be
taken. A typical usage is to start a timer after a port lookup
request has been issued:
public MyEgg
extends Egg {
private Timer timer;
public MyEgg() {
super(MyEgg.class.getName());
requestPort("emitter");
timer =
createSingleShotTimer(30000/*30 secs*/);
}
public void handle (Timeout t)
{
System.err.println("Help! Can't find 'emitter'!");
}
public void handle(LocatePortResponse r) {
timer.cancel();
Port p = r.getPort();
bindToPort(p);
}
public void handle(SomeBroadcastMessage m) {
respond ("Message received - over and out!");
}
}
Using repeating timers
Repeating timers are useful for performing periodic actions
(really!). A consequence of the queue-based delivery of messages,
though, is that if a handle method takes a long time to respond to a
timeout, the other timeouts may expire, be queued in the egg's message
queue, and be delivered to the egg in quick succession.
A More Complex Egg
Tips and Tricks