|
one.world Tutorial Part I The Counter
Application This part of the tutorial introduces and illustrates
Note that the one.world tutorial uses three browser windows: this window for the tutorial itself, another for source code, and another for class documentation. You can find the entire source for this example in
private static final ComponentDescriptor SELF =
new ComponentDescriptor("one.toys.Counter",
"The main counter application"
+ " component",
true);
The component descriptor specifies the name of the component, a short
description, and a flag for whether the component is concurrency-safe
(that is, thread-safe). Our main component needs to be
concurrency-safe, because it may have to process concurrent
events.
Next, we define the exported and imported event handlers for our
main component.
private static final ExportedDescriptor MAIN =
new ExportedDescriptor("main",
"The main exported event handler",
new Class[] { EnvironmentEvent.class,
Timer.Event.class,
DynamicTuple.class },
null,
false);
The descriptor specifies the name of the exported event handler, a
short description, the types of events processed by this event
handler, the types of exceptions raised by this event handler, and the
linkForced flag used during component linking. We
illustrate the use of environment events, timer events, and dynamic
tuples in the event
handling section of this part of the tutorial. In that same
section, we also explain the meaning of the link forced flag.
When the user closes the counter application's window, the counter
application needs to notify its environment that it has stopped. It
can do this by sending an event to the request event handler exported
by the environment. As a result,
private static final ImportedDescriptor REQUEST =
new ImportedDescriptor("request",
"The request imported event handler",
new Class[] { EnvironmentEvent.class },
null,
false,
true);
The descriptor specifies the name of the imported event handler, a
short description, the types of events sent to this event handler, the
types of exceptions sent to this event handler, and the
linkForced and linkOne flags used during
component linking. If the linkOne flag on an imported
event handler descriptor is true, not more than one
exported event handler can be linked to the corresponding imported
event handler.
Our counter application needs to be notified every second that it should increment its counter. Fortunately, one.world includes a timer component to provide us with timed notifications. So, the main component needs to import a timer event handler, to be linked to the timer component's request exported event handler. The imported event handler descriptor for the timer imported event handler looks like this:
private static final ImportedDescriptor TIMER =
new ImportedDescriptor("timer",
"The timer imported event handler",
new Class[] { Timer.Event.class },
null,
false,
true);
Before we continue with coding the main component, let's step back and review the structure of our counter application. The counter application employs three components. First, its main component (the only one we need to write), provides the actual functionality. Second, a timer component provides timed notifications. Finally, like every application in one.world, the counter application interacts with its environment. The following diagram illustrates this structure:
The arrows in this diagram represent the flow of events between imported and exported event handlers. The base of an arrow represents an imported event handler, to which events are sent, and the tip of an arrow represents an exported event handler, which accepts and processes sent events. Notice that event handlers have two names, depending on the view-point. Event handlers have one name in the component that imports them, that is, the component sending events. They have another name in the component that exports them, that is, the component processing sent events. The two names may be, but need not be, the same names. Back to coding the main component. We have defined the descriptors for the main component's imported and exported event handlers. But, to use the event handlers, we need the actual event handlers, not their descriptors. So, let's define an inner class to actually implement the functionality of the main exported event handler:
final class MainHandler extends AbstractHandler {
protected boolean handle1(Event e) {
// The event e is processed here.
return false;
}
}
MainHandler extends AbstractHandler,
because that class provides a reasonable default behavior for
exceptional conditions, as well as utility functions to simplify
responding to an event.
We also define the instance fields for the three event handlers
imported and exported by private final EventHandler main; private final Component.Importer timer; private final Component.Importer request;We declare the two imported event handlers to be instances of Component.Importer
and not to be just instances of EventHandler,
because Component.Importer provides useful functionality
beyond event handling, for example, the ability to test whether the
imported event handler is
linked.
Finally, we define the constructor for
public Counter(Environment env, String name) {
super(env);
// Initialize other instance fields.
main = declareExported(MAIN, new MainHandler());
timer = declareImported(TIMER);
request = declareImported(REQUEST);
}
The constructor for every component must take an environment as an
argument and must immediately invoke the superclass's constructor with
that environment as its argument. This process instantiates the
component within the environment. The constructor for our main
component takes an additional argument for the counter
application's name.
The You may already agree that writing out the basic structure of a
component can get tedious real fast. You need to define descriptors,
inner classes to implement exported event handlers, instance variables
for event handlers, and so on. Luckily, one.world includes a
handy utility, called The description for our counter application's main component is in counter.skel and the resulting skeleton source file is in Counter.java.
Before introducing all of private static final int INACTIVE = 1; private static final int ACTIVATING = 2; private static final int ACTIVE = 3;In the inactive state, our counter application is neither counting nor displaying its window. In the activating state, our counter application is getting ready to count and currently scheduling timed notifications. Finally, in the active state, our counter application is counting and displaying the current count in its window. Having defined the constants for private transient Object lock;The lock field provides the lock protecting accesses to
all other fields. We need it because Counter is declared to
be concurrency-safe. Generally, we prefer to define an internal,
explicit lock instead of using synchronized methods, because other
classes do not have access to an internal lock. Since
lock is an Object, it cannot be serialized
and thus needs to be declared transient. Declaring a
field transient indicates to Java's serialization
mechanism that the field should not be included in an object's
serialized state.
private String name;The name field provides the counter application's
name. It is assigned in the constructor as well as when the counter
application is cloned.
private int status;The status field provides the current status of the main
component, which must be one of the three constants defined above.
private long count;The count field provides the current counter value. We
chose a long, so that the counter application can count
for a very, very long time without rolling over.
private final EventHandler main; private final Component.Importer timer; private final Component.Importer request;The main, timer, and request
fields provide Counter's imported and exported event
handlers, which we already discussed in the last section. They are
declared final because, just like name, they
are assigned once in the constructor and never changed again.
private EventHandler cancel;The cancel field provides the event handler used to
cancel timed notifications. Its value is provided by the timer
component when it confirms a scheduled notification.
private transient JFrame frame; private transient JLabel label;The frame field provides a reference to the counter
application's window and the label field provides a
reference to the label showing the current count. Both fields are
declared transient, because Swing GUI elements are not
meant to be stored in serialized form. Yet, applications in
one.world can be check-pointed, storing all their internal
state in persistent storage.
When a new Java object is created, all instance fields are assigned
well-defined default values, such as
public Counter(Environment env, String name) {
super(env);
lock = new Object();
this.name = name;
status = INACTIVE;
main = declareExported(MAIN, new MainHandler());
timer = declareImported(TIMER);
request = declareImported(REQUEST);
}
The newly presented code simply creates a new lock object, assigns the
passed in name to the corresponding internal field, and initializes
the main component's status to INACTIVE.
By deciding which fields to declare During serialization, Java's serialization mechanism reads an
object's non-transient fields and converts them to bytes. While this
is just the behavior we'd like to see, all field accesses,
including those by Java's serialization mechanism, need to be
protected by
private void writeObject(ObjectOutputStream out)
throws IOException {
synchronized (lock) {
out.defaultWriteObject();
}
}
If an object defines such a method, Java's serialization mechanism
calls the writeObject() method to serialize the object
instead of accessing the object's non-transient fields directly. The
above writeObject() method simply invokes the default
serialization mechanism while holding Counter's lock.
When a Java object is deserialized, all transient fields are
assigned their default values. To overwrite this behavior, we can
define an explicit
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
lock = new Object();
}
If an object defines such a method, Java's serialization mechanism
calls the readObject() method to deserialize the object
instead of restoring the non-transient fields directly. The above
readObject() method simply invokes the default
deserialization mechanism and then assigns the lock field
a new object. Note that it does not restore the main component's
frame and label fields. Restoring a running
counter application's window is left to the event handling logic,
because it can take a relatively long time and thus seriously slow
down deserialization.
If you want to find out more about serialization, Sun's object serialization page includes the specification, which defines all the gory details of Java's serialization mechanism.
To send an event to an imported event
handler, we "simply" invoke the event handler's
DynamicTuple dt = new DynamicTuple();
dt.set("msg", "incr");
timer.handle(new
Timer.Event(main, null, Timer.SCHEDULE, Timer.FIXED_RATE,
SystemUtilities.currentTimeMillis() + 1000,
1000, main, dt));
This piece of code sends a timer event to the timer component,
requesting timed notifications every second starting a second from
now.
The first two arguments to The next four arguments to SystemUtilities.currentTimeMillis()instead of Java's System.currentTimeMillis()to determine the current time, because
The last two arguments to In the background section, we said that events are passed by value. one.world does not always enforce this, because it would introduce too much overhead into the system. Consequently, it is good programming style to create a new event when invoking an imported event handler and to not retain a reference to that event in the sending component, just as we did in the above piece of code. To process sent events in an exported event handler, we typically
define an inner class that extends Our main event handler needs to process three types of events: (1)
environment events
sent by the counter application's environment, (2) timer events sent by
the timer component to confirm the scheduling of timed notifications
and (3) dynamic tuples sent by
the timer component to notify the main component that a second has
passed. We need to process environment events in our main event
handler, because it is linked to the counter application's main
imported event handler. We need to process the latter two types of
events in our main event handler, because we used Here is the code for processing environment events.
if (e instanceof EnvironmentEvent) {
EnvironmentEvent ee = (EnvironmentEvent)e;
if (EnvironmentEvent.ACTIVATED == ee.type) {
// Start the activation process.
start();
return true;
} else if ((EnvironmentEvent.RESTORED == ee.type) ||
(EnvironmentEvent.MOVED == ee.type)) {
// Re-display window.
show();
return true;
} else if (EnvironmentEvent.CLONED == ee.type) {
// Reset name.
name = getEnvironment().getId().toString();
// Re-display window.
show();
return true;
} else if (EnvironmentEvent.STOP == ee.type) {
// Deactivate counter.
stop();
respond(e, new
EnvironmentEvent(this, null,
EnvironmentEvent.STOPPED,
getEnvironment().getId()));
return true;
}
}
Activated environment events notify an application that its
environment has been activated and it can start running. When
receiving an activated environment event, an application typically
starts the process of acquiring any resources it might need to get its
job done. Our counter application, in Restored, moved and cloned environment events notify an application that it has been restored from a stored check-point, moved to a different node, or cloned from another application, respectively. In all three cases, the application was running before the corresponding operation and has just been deserialized from its serialized state. When receiving a restored, moved, or cloned environment event, an application typically restores any resources, such as a GUI or access to tuple storage or a communication channel, that are not part of the application itself. Our counter application redisplays its window, which we did not restore during deserialization. When the counter application has been cloned, it also resets its name, since the name reflects the ID of its environment. Stop environment events notify an application that its environment
is about to be terminated and that the application must stop running.
When receiving a stop environment event, an application should release
all resources it has previously acquired and then respond with a
stopped environment event. Our counter application, in
Keep in mind that every application must export an event handler, which is linked to its environments main imported event handler and which processes the above five types of environment events! We have discussed how our counter application transitions from the inactive to the activating state and from the active to the inactive state. What about the transition from the activating to the active state? Our counter application makes this transition when timed notification have been successfully scheduled. Here is the corresponding piece of code, which processes timer events.
if (e instanceof Timer.Event) {
Timer.Event te = (Timer.Event)e;
if (Timer.SCHEDULED == te.type) {
// Display window.
run(te.handler);
return true;
}
When receiving a scheduled timer event, our counter application has
acquired its only resource, timed notifications. In
run(), it transitions from the activating to the active
state by displaying its window. Note that our counter application
retains a reference to the event handler specified by the scheduled
timer event in the cancel field, so that it can later use
that event handler to cancel timed notifications.
The event handling logic of
if (e instanceof DynamicTuple) {
if (e.hasField("msg") && "incr".equals(e.get("msg"))) {
// Increment count.
incr();
return true;
}
}
When receiving a dynamic tuple, whose msg field has
incr as its value, our counter application knows that it
is time: it increments its count and displays the latest value.
We are not going to review the implementation of the
Synchronous.invoke(c,
new Timer.Event(null, null, true),
Constants.SYNCHRONOUS_TIMEOUT);
When stop() returns, our counter application must have
completely stopped, because we need to confirm to the environment
that, yes, the application has stopped. As a consequence, just sending
a timer event to cancel timed notifications is not sufficient. Rather,
we need to wait until the cancel timer event has been confirmed. We
use Synchronous.invoke()
to perform such a synchronous invocation. invoke() calls
the specified event handler with the specified event, setting the
source of the event to some internal event handler. When the internal
event handler has received an event or when the specified time-out has
passed (which ever happens earlier), the synchronous invocation
completes and returns the event or null, respectively.
Our counter application, in either case, assumes that timed
notifications have been canceled, returns from stop() and
declares itself to have stopped.
Having discussed our counter application's event handling logic, it is now time to peel back the covers and take a closer look at one.world's event handling machinery. Let us assume that component A has an imported event handler, which is linked to an event handler exported by component B. Now, component A sends an event through its imported event handler to component B's exported event handler. How can we implement this event passing? The simplest way is to use the current thread of execution and to just call the exported event handler with the sent event as its argument. By default, this is exactly what happens in one.world when one component sends an event to another component within the same environment (i.e., within the same application). The following diagram illustrates this straight-forward flow of events. ![]() The biggest advantage of the direct implementation of event passing is that it is simple and fast; after all, we are performing only a method call. However, it suffers from three problems. The first problem with the direct implementation is that some event exchanges will break it. Consider, for example, two components that ping-pong events between them: Component A sends an event to component B, which responds with an event to component A, which responds with an event to component B, and so on. With the direct implementation, every sent event is implemented by a method call and in a ping-pong exchange none of these method calls return. Since every method call adds a new stack frame to the current thread's stack, the stack will eventually overflow, just like for any infinite recursion. The second problem with the direct implementation is that, while event passing itself is fast, the component sending an event needs to wait for an indefinite amount of time before resuming its work. The component sending an event needs to wait, because the current thread is busy executing the code of the component that received the event. This is an issue, for example, for the implementation of the timer component used by our counter application. If the timer component used the direct implementation of event passing, its thread might be executing the code of a component that just received a timed notification, while it should really send another notification to some other component. The third problem with the direct implementation is that it makes it really hard for us, the implementors of one.world, to isolate applications from each other and from the core system. If the same thread executes code from different applications and the core system alike, it is hard to activate and terminate applications independently and to protect the core system from misbehaving applications. The solution to all three problems relies on a single mechanism, which we call an animator. An animator has a queue, storing pairs of event handlers and events, and a set of one or more threads. Each thread continuously processes the queue by removing an <event handler, event> pair from the head of the queue and by invoking the event handler on the event. Using an animator, event passing can be implemented as follows. When component A sends an event to its imported event handler, the corresponding event handler exported by component B and the sent event are added to the tail of the animator's queue. Component A does not wait for component B to receive the event; rather, it immediately resumes its own work. Once the <event handler, event> pair has moved to the front of the queue, component B's event handler processes the sent event in its own thread. The following diagram illustrates the flow of events mediated by an animator. ![]() Since event passing through animators uses separate threads that do not wait for each other, we call this style of transferring control "asynchronous events". Now that we have looked at the two implementations of event passing, let's take a look at how the two implementations are used. In one.world, each environment has its own concurrency domain, which provides the animator for all components within that environment. Since the actual environments, that is, the code that performs operations on environments, are privileged, the environments themselves have their own concurrency domain, provided by the root environment (the "kernel" of one.world). When linking an imported and an exported event handler, the linker determines whether to use the direct implementation of event passing or the animator-based implementation. If the two components to be linked are in different concurrency domains, all event passing is always mediated by an animator. The linker automatically inserts the appropriate code. For example, when linking our main component to its environment (we'll show how in the initializer section), the linker uses the animator-based implementation of event passing, because the main component and its environment are in different concurrency domains. When linking two components that are in the same concurrency
domain, the direct implementation of event passing is used by default.
However, this default can be overridden by (1) setting the
Asynchronous events are a good fit for applications that need to
react to real world events, such as a meeting being started or a
person leaving a room. However, asynchronous events can also make it
harder to implement synchronous interactions, that is, interactions
where we need to know that an event has been received by the intended
receiver or that a requested operation has been completed. We already
saw an example for a synchronous interaction in In fact, applications frequently need to synchronously schedule and
cancel timed notifications. To make our lifes easier, the Furthermore, applications frequently need to time out
request/response interactions and retry such an interaction if it
fails. Instead of using
In one.world, applications are loaded by so-called initializers. It is the initializer's job to instantiate the components of an application (the timer and main boxes in the diagram) and to link them with each other (the arrows in the diagram). An initializer is implemented by a static method with signature: public static void init(Environment env, Object closure);The environment passed to an initializer is the application's environment and the closure is an optional argument provided by the program loading the application. If an application is loaded from the root shell through the mk or load commands, the closure is an
array of strings, just like for the main() method used to
run regular Java programs.
An application's initializer may throw any Java throwable to signal that it could not properly initialize the application. In that case, the environment's imported event handlers are automatically unlinked, which effectively undoes all operations performed by the initializer. The initializer for our counter application is implemented by
public static void init(Environment env, Object closure) {
Counter comp = new Counter(env, env.getId().toString());
Timer timer = new Timer(env);
env.link("main", "main", comp);
comp.link("timer", "request", timer);
comp.link("request", "request", env);
}
As already suggested above, Counter's initializer first
instantiates a new main component and a new timer component. It then
links the main component, the timer component, and the application's
environment.
Since applications commonly use timers to detect timeouts for
request/response interactions with asynchronous events, each
environment has its own timer component. This timer component is
accessible through the
Timer timer = comp.getTimer();
If you are using the source distribution of one.world, you
first need to build all Java class files. Consult the development setup page and the FAQ on how to setup your build
environment and how to build one.world from source. You also
need to change the startup scripts (" If you are using the binary distribution of one.world, you are ready to rumble. No building and no configuration are necessary. To run one.world, execute " You can run the counter application from the root shell by executing: mk counter one.toys.Counter run counterThe first command creates a new environment named " counter" and runs the initializer defined by class
"one.toys.Counter" in this environment. The initializer,
in turn, instantiates the counter application. The second command
activates the counter application.
If you want to see serialization in action, try check-pointing a running counter application. While the counter application's window is being displayed, execute checkpoint counterand remember the displayed counter value. Now, exit one.world by executing exitand then restart one.world again. You can restart the counter application from the saved check-point by executing restore counter -1in the root shell, which will restore the counter application to the latest saved check-point. Happy, happy, joy, joy! |