|
Multi-Threaded Object Network Architecture
|
|
Main MTON Features
-
It can instantiate, simulate, and debug any port based object network.
-
About 70 classes in ~10K lines (only the simulation engine).
-
Designed to be executed as a remote daemon process, accessible from a TCP
socket. But it also can run locally as a full-featured shell system, apart
from the Graphical User Interface.
-
Support to dynamic class loading through remote class servers.
-
Each network place runs on its own thread.
-
Concurrent control is intrinsically implemented into the iteration sequence,
even with external Java classes.
-
Ordinary Java classes can be imported, and then be used inside the MTON
engine as if they were pure object network classes. In this case a set
of Java interfaces and abstract classes is provided.
-
Appropriate exception handling for exceptions thrown from user code (user
exceptions) and thrown from internal MTON code (critical exceptions).
How the MTON engine works
The MTON engine is composed, basically, by a huge set of abstract
classes, interfaces and support classes. The first thing you need to enable
the MTON engine is the compiled Java package of the desired object network.
These classes contain information about topology, classes (including their
Java implementation) and kernel objects. All this stuff is generated by
means of the Graphical User Interface.
Direct accessing the MTON engine is a very complicated task, requiring
several protocol requirements. In order to facilitate this communication
we developed an interactive console, with a textual interface. This console
can be controlled locally by the user or be redirected to a TCP socket,
allowing remote control. When it runs as a remote console server it acts
like a daemon, allowing it be started in the same virtual machine of the
GUI (default setting) or in another remote JVM.
The engine is needed only when simulating a network, because the
process of edition and compilation can be totally carried out under the
front-end.
Loading a compiled network into the console
Whenever the user wants to simulate the network, which is supposed
to be already compiled, it does so by executing the appropriate option
in the GUI. This causes the loading of the network into the console. However
it's almost sure that the classes that define the network are not accessible
to the console class loader. This way, it relies on its on-demand remote
class loader, which is responsible for loading all needed Java classes
it may need to instantiate the network. It does so through successive connections
to an available file server. This file server runs as a daemon on the GUI
JVM, which, in turn, has direct access to the files that define the desired
package. This process is totally transparent to the user. You will need
to be aware of this fact only when working with the textual console interface.
The figure bellow shows the entire process. The ONSL compiler generates
all compiled Java classes that implement the network and make them accessible
through its local class server. Then the user requests the start of the
simulation, causing the console to load the supplied network through the
class server and instantiate it in its local JVM.

Console Command Summary
server HOSTNAME:PORT |
set the current class server. The default value is set to localhost:5000. |
load NETWORK |
instantiates a network. The supplied network must be readable
by the current class server. |
down |
performs a shutdown and remove the current running network from the
VM. |
step [N] |
performs N iterations on the current network.
If omitted N=1. |
watch PAGE COMMAND |
adds a watch entry on the specified page. |
watch PAGE (on|off) |
turns on/off the specified watch page. |
unwatch PAGE ENTRY |
removes a watch entry from the specified watch page. |
[OBJECT].METHOD |
invokes a debugging method on the specified object. If the object
name is omitted then will be used the current namespace. Depending on the
method can be necessary one or more arguments. |
cd (..|DIRECTORY) |
changes the current directory. The directory specification does not
support inline use of '..' aggregated to another name. |
pwd |
prints the current namespace directory. |
buffer [on|off] |
sets buffering on/off or prints the current state. |
echo [on|off] |
sets echoing on/of or prints the current state. |
debug [on|off] |
sets internal debugging on/off or prints the current state. |
config |
prints all current available configurations. |
help |
shows short descriptions of all commands. |
dump (l|f) |
dumps the entire network namespace tree as a string (or collection
of strings). The 'list' format shows a listing like 'ls -lR' unix
command, with one entry for each nameable. The 'functional' option generates
a listing in a PROLOG like format, packing everything in one string. This
command is used only by the inspector when it wants to discover all details
of the current network at one shot. |
Exception Handling
During simulation several pieces of code inside the MTON engine
and the user code may throw exceptions at any time. The engine considers
all exceptions to be of two different types:
-
- User exceptions: these exceptions were thrown either directly
from the code the user has typed in the code editor or some stub method
the user code has invoqued. This type of exception is very common during
debugging. The default action performed by the engine when it catches an
user exception is to kill all MTON engine threads. The console (and the
console server) unload all network classes and keeps itself on-line, waiting
more command requests. From the simulation dialog side the exception is
reported.
-
- Engine exceptions: these exceptions are thrown intrinsically by
the internal code of the engine, and has nothing to do with the user code.
But they may also be thrown due incorrect implementations of external Java
classes on the Mutable and Immutable
interfaces. The default action performed is abort the Java Virtual Machine,
because it implies in a serious MTON error. Whenever this happens one should
send an error report to the maintainer of the code, as described in the
report that the failure generates on the console the Console Server
was started.
It is recommended that all possible throwable exceptions be appropriately
treated inside the user code, even in known error conditions, in order
to free any previous allocated resource.
Importing external Java classes
Introduction
The MTON engine has the ability to import ordinary Java classes
into its execution system, allowing reuse of previously available code.
In order to be imported a Java class must implement the ExternalObject
interface. This interface defines the basic behavior of an external object.
Previous releases of the simulation engine treated data safety internally,
however this feature has changed significantly. We have restructured this
process to avoid racing conditions that could be raised from the engine
during simulation. Now the engine leaves almost all necessary runtime checking
to the external Java class itself. This may seem a poor approach, but it
brings more flexibility to the system itself, because now the user is free
to adopt all, some, or none of the safety recommendations at all.
The ExternalObject interface was designed to provide some minimum
concurrent control over objects running in several threads. For convenience,
we provide an abstract default implementation. Meanwhile, due the fact
that the Java language does not support multiple class inheritance (only
one class and zero or more interfaces) it's not guaranteed that you always
will be able to use this default implementation. This means that sometimes
will be necessary apply a kind of "cut and paste" of the code from the
default implementation to your code.
Native network objects do not need special treatment, because their
concurrence control is embedded in their code.
Becoming MTON-able: initial view
The iteration sequence may be primarily divided into two fundamental
stages. The first is the match, in which all possible combinations are
tried by all interested consumers. The point here is that NO, I said no,
state modification - on any object - can be performed during this phasis
because no-one can predict the order in which interested consumers will
try their match. The second is the perform phase, after all conflicts being
solved enabled objects will perform assigned operations. Even now we need
to be aware of which kind of modifications - and over which objects - are
to be done, because objects may be consumed in several different ways by
consumers. When an object is shared it cannot have its state changed.
We are aware that concurrency control is one of the hardest things in
computer programming. In this project we have done a deal, in the sense
that we provide the user with basic information needed to perform the checking
for invalid state changes. On the other hand the user can play with this
trade depending on the costs of this control. These problems will be commented
and treated later on this section.
The initial step to adapt an external Java class is to pay some bureucracy,
implementing the ExternalObject interface. It will be necessary
the implementation of three methods (one is due the inheritance from NetCloneable).
The code must import the package as:
import netobj.MTON.*;
The main interfaces are:
public interface ExternalObject extends NetCloneable
{
public void setOwner(ExternalNetObject owner);
public ExternalNetObject getOwner();
} |
and
public interface NetCloneable {
public NetCloneable createClone();
} |
ExternalNetObject getOwner()
Returns the owner of this external object instance. Actually
this value is the same previous value set by setOwner, or null
if this object has no owner.
void setOwner(ExternalNetObject owner)
Sets the owner of this object. If this object is being removed
the owner should be set to null. The value of owner should
be stored internally in the current instance.
NetCloneable createClone()
Creates a new instance that has the same values of this one
(derived from the NetCloneable interface).
Meanwhile, all this process may be cumbersome if you need to recode it
several times in your classes. As a matter of convenience we supply a default
implementation for the ExternalObject interface. Note that this
implementation takes into account thread synchronization.
abstract public class DefaultExternalObject
implements ExternalObject, Cloneable {
/** Owner of this object. */
protected ExternalNetObject __owner;
/**
* Gets the owner of this object.
*
* @return owner of this object.
*
If this object has no owner it will
*
return <code>null</null>.
*/
public ExternalNetObject getOwner() {
synchronized(this) {
return __owner;
}
}
/**
* Sets the owner of this object.
*
* @param owner new owner of this object.
*/
public void setOwner(ExternalNetObject owner)
{
synchronized(this) {
__owner = owner;
}
}
/**
* Creates a clone of this object.
* @return clone of this object.
*/
public NetCloneable createClone() {
try {
return (NetCloneable)clone();
} catch (CloneNotSupportedException
e){
Throwable re =
new RuntimeException("could not create clone.");
throw (RuntimeException)re.fillInStackTrace();
}
}
}//DefaultExternalObject
|
This implementation provides a wrapper from the method netobj.MTON.createClone
to java.lang.Object.clone, which is better undertood by
Java programmers (but in this release they have equivalent functionalities).
This way you can forget createClone and implement only clone.
So we have that:
protected Object clone() throws CloneNotSupportedException
This method is derived from the Cloneable interface,
and returns a copy of this object.
Even with this default implementation we may be in trouble with the limitation
of the single inheritance imposed by the Java language. This will be clearly
perceived when your candidate external class is already a sub-class of
another class, making impossible put DefaultExternalObject as
a base class of it. This situation can be solved simply copying the internal
code of the default implementation to your class and implementing the clone
method.
It's very important to note that we still did not perform any state
tracking. We just studied basic features.
Becoming MTON-able: ensuring data safety
The next step is to evaluate how states are arranged, how they change,
and who can trigger state changes (who is invoking which methods?).
The MTON engine can handle two types of instances regarding
state behavior.
-
Mutable, instances that can change have its state changed.
-
Immutable, instances that, once created, will retain that same value
until it be destroyed by the garbage collector.
The simplest case is the immutable one, as they cannot change anyway, no
concurrent sequence of events can raise problems at all. As a rule of thumb,
just sub-class the DefaultExternalObject (or DefaultMutable
if you are one of those purists, because they are the same).
The mutable case is certainly painful due the very fact that now we
depend on the way the user has designed the external class and the particular
way in which the JVM schedules concurrent threads. We have no fully guaranteed
recipe to solve all these problems. The MTON engine supplies the external
class designer with some runtime information to avoid at least pathological
cases, e.g., those ones that happens when some state change is to be performed
when:
-
the network is inside the matching phase;
-
the target object has one or more weak references (attachments) to it.
We stress that the internal simulation engine does not care about all this
stuff if you don't. This is just an advice to prevent, with the greatest
probability, any possible misbehaviour. We can, in fact, design fully predictable
classes that will never raise data safety problems. Sometimes this will
be in fact the only possible solution (see the Matrix class),
where references to the internal array may be used from other object making
verification virtually impossible.
We provide a basic default class to help with mutable classes. This
class makes use of the information supplied by its wrapper (an instance
of ExternalNetObject) that sinalizes if the state can or cannot
change depending on the type of reference is being realized over it.
abstract public class DefaultMutable extends DefaultExternalObject
implements Mutable {
/**
* Determines if this object can change
its internal
* state. This information is obtained
from the
* parent, invoking its <code>canModifyState</code>
* method. Notice that invocations to this
method
* should be synchronized on this object.
* @return true(free to modify state),
false(denied).
*/
public boolean canModifyState() {
if (__owner != null)
synchronized(__owner)
{
synchronized
(this) {
return __owner.canModifyState();
}
}
return true;
}
/**
* Throws an exception if this object cannot
have
* its internal state changed.
* @exception ConcurrentException if this
object
*
cannot be change.
*/
public void assertModifiable()
throws ConcurrentException{
if (!canModifyState())
throw new ConcurrentException(CANNOT_BE_MODIFIED);
}
} //DefaultMutable |
Remember our discussion on the problem of the single inheritance of
Java. Sometimes will be required that you use the cut and paste technique.
The following listing shows a possible implementation of a class designed
to hold an integer value, allowing some operations on it. In this case
we are free to sub-class DefaultMutable.
import netobj.MTON.*;
public class xInteger extends DefaultMutable {
private int value;
public xInteger(int value){
this.value = value;
}
synchronized public int getValue() {
return value;
}
synchronized public void setValue(int i) {
assertModifiable();
value = i;
}
synchronized public void add(int value) {
assertModifiable();
this.value += value;
}
protected Object clone() {
return new xInteger(value);
}
} //xInteger |
Notice that in this simple example we have simply made all
methods synchronized just to avoid some complications. Not all cases are
simple as this, because they eventually may fall into deadlock.
Network dynamics
Object Encapsulation: Fundamental concepts
One of the most important things regarding dynamics is the object encapsulation.
It specifies how objects are structured, and how their fields access other
objects.
The first concepy you need to know is that there are two kinds of reference,
connecting an object to a field of another object: structural and
weak references.
Structural references indicate that a given object A in fact belongs
to a object B, allowing it to change the internal state of A. Any other
object even cannot hold a reference to it. Weak references state that a
given object A is referenced by one or more objects B1, ...,
Bn, but no one can change the internal state of A, as its value
is shared between all participants.
Putting these ideas more formally:
-
An object O1 is said to be the owner of O2
iff O1 is the unique object that has a reference
to O2 and the permission to change the internal state of it.
-
An object Oa is said to be attached to O1,
O2,.., On iff each object in O1,
O2,.., On contains a reference to
Oa,
but no permission to change the state of it.
Weak references (attachments) are intensively used during the match
process, when several combinations are evaluated concurrently in several
threads. As it does not allow state changes during the existence of weak
references, candidate consumers cannot, in any way, alter the internal
state of incoming objects. This reference was mainly designed in order
to avoid unnecessary object cloning when objects are shared by consumers.
Another useful application takes place when an object is consumed in shared
mode, does not requiring unnecessary duplications.
Structural references indicates that the object is virtually contained
in the field of another one. As no other object has a reference (weak or
structural) its state can be freely changed by the owner at no data safety
risk.
The following figure shows this process.
The simulation engine takes into account the type of reference
an object has to decide whether it can or cannot haver it internal state
modified (see the section: importing
external classes).
If you have any
suggestions click here.
last update: 04-aug-1999
$Id: mton.html,v 1.1.1.1 1999/09/23 01:16:35 asrgomes Exp $
|
|