Home | Papers | Reports | Projects | Code Fragments | Dissertations | Presentations | Posters | Proposals | Lectures given | Course notes |
Refinement of the SEESCOA Component ArchitectureWerner Van Belle1* - werner@yellowcouch.org, werner.van.belle@gmail.com Abstract : This document describes the refinements we have made to the component architecture over the past 9 months. First we describe some of the esthetic things we've added to the system. These smaller refinements include the addition of a port principle. We've changed the initialisation procedure of components and other small things. In general this first section describes how one can use the component system now. The second part of this document describes some larger design enhancements, mainly to see with reification of components. This section delves a bit deeper into the component system. It discusses the why and how of the architecture. The last part describes how the component system has been made to work in a distributed context. This section also discusses why we didn't use Jini for making the component system distributed.
Keywords:
meta level architecture connection oriented open distributed system seescoa component system |
This document describes the refinements we have made to the component architecture over the past 9 months. First we describe some of the esthetic things we've added to the system. These smaller refinements include the addition of a port principle. We've changed the initialisation procedure of components and other small things. In general this first section describes how one can use the component system now. The second part of this document describes some larger design enhancements, mainly to see with reification of components. This section delves a bit deeper into the component system. It discusses the why and how of the architecture. The last part describes how the component system has been made to work in a distributed context. This section also discusses why we didn't use Jini for making the component system distributed.
The component system is the infrastructure (framework, architecture, or kind of operating system), which makes component instances work together, which glues them and creates a homogenous environment for them. The component ssytem can be seen as the middle-ware which connects different components and which makes them work together.
The component systems offers this functionality by means of asynchronous message sending. This was documented in deliverable 3.3b. []
One of the first things we absolutely needed was the ability to run the component system and the demos on the camera-board. This was no problem, except for some minor version mismatches between the Java virtual machines. Notable were the problems with different versions of the AWT.
A second problem we had to solve was the long development cycle. Writing code, sending it to the client, starting and debugging takes a long time if you don't have a good synchronisation tool. Therefore we added CVS to the board which allowed us to check out a .jar file which can be run immediately. This jar file is made on the deployment box by a simple make command with an automated CVS commit.
Booting the system is of course also a problem. We don't want a component 'system' which should be loaded from within the main progam. Instead we want a component architecture which is able to load other components, also including a main component. The component system is now started by loading the ComponentSystem class.
The parameters given to the main program are first checked for scheduler parameters. Possible schedulers are the Standard scheduler (std) , and an Earliest Deadline First scheduler (edf), although we didn't check whether this one still works or not. There is an experimental StupidScheduler (dumb) which doesn't guarantee orderly delivery and an extremely fast but single threaded OneThread scheduler (one). If no scheduler is given the standard scheduler is used. If we choose the standard scheduler or the stupid scheduler we can specify how much threads should be used at the same time. Examples are given below:
If we want to trace messages or want to time messages we can use the special message fields at the command line. Tracing is done by specifying messages:trace, timing can be done by using messages:time. The minimum, maximum and average times are recorded. The total duration and count are also tracked.
Now, if we boot the component system we often want to specify which components should be loaded. To do this we specify a component field, that contains the blueprint (the full class name) of the component to be loaded. After that we specify the name of the instance we want this component to have. For example, if we want to load the Httpd example [] we can do this by
There are some issues which have changed. (The reason why will be discussed in the Internal Design of the Component system). One of these is the creation of components. Instead of calling a CreateComponent upon the component system, we now send a CreateComponent to the ComponentSystem.
The Component System component is a component which interfaces the component system to any other component. While designing the case we encountered the need to retrieve information from and put information into the component system in an uniform way. A uniform way in the sense that we 'send a message' to the component system, and not 'call a method upon the component system'. We needed the ability to treat the component system as if it were a component itself. This was necessary to make distribution easier. The actions the component system component should be able to undertake are
When creating a new component, we ask the component system to create a new component from a given blueprint. This is done by sending a message to the component system. This is not done by calling a method upon the local component system. This allows us to send a CreateComponent message to another component system.
When a new component system connects, we want to receive a notification of this event.
Belowm we discuss the interface a component will be able to use at the componentsystem.
The component architecture is packaged with a tool, a component transformer, which is needed to convert .component files to .java files, which in their turn can be compiled. This tool has received two extra keywords, which are questioned at the moment of this writing. The first keyword is port, the second multiport. The port can be used instead of String. Multiport is a keyword which expands to Multiport, which is a descendant of a Vector. Anything send to a multiport will be delivered to all subscribed parties. Other components can connect to a multiport by sending a Connect message to a component. Eg. Component a is written as follows:
A..Connect(<Port:``b''>, <With:A>)
This section describes the internal architecture of the component system. In this section we will illustrate the missing features of the original component system as well as the quite general solutions we offer.
But first, we have to discuss the failure of the existing infrastructure. The existing infrastructure was not good enough because some very simple things were very hard to achieve. Below we summarise a number of shortcomings:
We improved the message handlers first by rewriting them completely. The goal of a message handler is the ability to see what a component sends out and to see what a component receives. On top of this the message handler should be able to filter the message at hand or reroute it.
Catching received messages is not really a problem in our system, we can easily reassign the receiver of a component to point to another component. Eg. If the component system has the name Alfa linked with component alfa and component Beta wants to see whatever Alpha receives it can ask the component system to create a new name Yamma which is bound to alfa. After that it rebind beta to Alfa, such that Alpha points to the actual component beta. If this is done, all messages send to the component ``Alpha'' will be received by component beta. Of course, instead of immediately handling the messages the component system should send a ReceiveMessage message to this proxy component. Beta can now look at the message and send it through to Yamma, which was the original A. This operation can be repeated as many times as necessary.
Now, when trying to catch the outgoing messages of a component we encountered a more difficult problem. Object Oriented languages are driven by the messages send to other components, they are not driven by the messages received. This means that we can relatively easy override the receiver of a component by acting as if, but it is quite difficult to catch outgoing messages since sending of messages are done immediatelly. We didn't want this ugly discrepancy, we wanted as much the ability to intercept messages being send as messages being received by a component. Even worse, we wanted to offer this in the same way. Just like an object first sees what a component would have received, we want to see what a component would like to send before it is actually send.
To summarise, we see how all components can act as a sending or receiving proxy for a component. If they are acting as a proxy they receive ReceiveMessage and SendMessage messages. If they want to do something, like lets say, removing the message or passing it to somebody else, we have to look at the message. To both procedures the message is passed in the <Message> field.
The first possibility to add in the component system was for every component a set of components which should see every message before it actually reaches the targeted component. This does not work because it is very difficult to intercept and stop messages this way. Furthermore the component system has a bit too much state in this scenario. If we want to make this distributed, things become much more difficult. So, a meta-level linked list is no good solution for catching incoming messages. Nevertheless, nothing stops you from creating your own object-level linked list by re-binding component names.
If we think about this solution for sending messages we also see the problem of intercepting outgoing messages, so this also is not useful.
The possibility we finally use is a set of tuples, every tuple containing three fields. The first field is the component which will handle the message, the second field is the component which will be used to send a message and the third component is the actual component which is overridden by both proxies. Proxying incoming messages is done by replacing the name of a given component with a new tuple. Catching the send messages is easily done by specifying a new sender-handler.
We will illustrate this below. Suppose we have component A which needs to be proxied for incoming messages by proxy B, Component C is the proxy for all outgoing messages. The original table 3.2, with nothing special in it. Table 3.4 shows how B becomes a proxy for A and table 3.6 shows how A is proxied by B and C.
|
|
|
Of course, the small tables from above are not suitable if we want 20 sender-listeners on one component. Therefor we might think of introducing a cross table in which we have on the Y-axis the from field and on the X-axis the target field. In every box we put a sendmessage and receivemesssage notification, just as above. Of course this system takes unbelievable much space and is not much more flexible.
A more flexible solution, but more time consuming is using something like IP-tables in which we specify a pattern of messages which will be translated to something else. Of course the problem with this kind of tables is their lack of management. It is quite easy to say that everything from component C to component B should be translated to ReceiveMessage upon D, but what happens if we create a proxy for C, either by redirecting everything first to us or by renaming C. In both cases we have unwanted behaviour. In the first, we see how it is impossible to add another extra proxy, in the latter we see how all rules already applying to C become invalid after renaming C.
If we look at the meta object protocol of object oriented languages
we see things like sending messages waiting for the answer and
continuing.
Even the most simple (and accordingly most powerful) meta object
protocols[]
use a system in which the basic behaviour consists of sending a
message.
Nevertheless they are all synchronous. This doesn't work, therefore
approaches like [] are quite useful. Below is the
description of the meta level protcol used in the component system.
The working of the interpreter is described in the msc in figure 3.1.
This meta level communication doesn't offer immediate connections. It is connectionless so to say. Of course, as everybody knows it is easy to add a connection-oriented protocol on top of connectionless protocols. Since we want to do this in an innovative way (with Contracts and Synchronization constrains placed upon these connections) we have to explain how we can create ports, multiple ports and multiports with the componentsystem.
The idea is simple, ports, multiports and multiple ports are also components. They have the name of the component to which they belong but are suffixed with the name of the port. So if component A has a port b,c, both ports will be components, called A/b and A/c. These two ports should be created by component A in the Init method. This is illustrated in figure 3.3.
This allows very interesting uses of ports: we are able to see which messages are sent and received on a certain port by wrapping it an assigning new message handlers. This way some remote actions can be triggered if a port sents out something. Of course, this kind of usage is not advised because we better create an immediate link with the component in this case.
Another interesting use, is the use of higher level ports which offer some kind of subscribe/notify mechanism. Of course, the current component sytem offers special syntax for this, but we might as well remove this syntax because everything (and even more) can be done by using these kind of ports.
The refinement of the component architecture also included a subtask which aimed at developing a distributed component system. We will now illustrate how easy this is done using the new meta-level architecture described above. But first, we need to have a look at properties of distributed systems.
A major question we have to ask ourselve when faced with the perils of distributed computing is whether we want to use a client server model or a peer to peer model. Both models have their strengths and disadvantages.
Client server computing allows us to offer a more or less secure environment in which we can control errors and in which one server knows what's happening. The backside of this is that most of the computers of the network are not used to their full capacity. This is where peer tot peer computing comes in.
Peer to Peer computing is a model in which all clients use and offer services to other clients. These can be seen as components sending messages to each other and reacting to incoming messages. Since we are using a component based development we would like to use peer to peer communication, with all the problems it brings with it.
The biggest problem of peer to peer communications is the total chaos it creates. Components absolutely don't have a clue regarding the overall computation being performed. Nothing is wrong with that of course as long as the system does what it is designed to do. Problems occur whenever components start failing and whenever something bad happens to the system. In normal computer systems we either have the whole system failing or nothing failing, in distributed systems we can have at any moment in time a failure of part of the system. This is bad, because we cannot predict what will fail, nor can we devise a good error handling strategy to solve this.
So, how bad is this ? The badness depends on two things. First: how many components are there. If there are a lot of components running throughout the system we will see the probability of a component failing rising.[] The second thing is the rate at which the network fails. If it fails a lot, it is almost completely unmanageable to write correct working software. We might need a better data transfer control protocol than the ones being used and the ability to recover components which died. But this dependent very much on the quality of the environment, the quality of service required by the application so to say.
When making the component system distributed we will not create a new communication protocol, we will use a standard naming service (the java RMI registry) and use that one to find other partners. All component names will be prefixed by the name of the component system they are running on.
If we know the address of one of our communication partners we connect to it and as long as the connection exists we take it for granted that the other component is still functioning correct. If the connection is broken the component system will send a notification to all subsribed components. So, the only client/server architecture is the naming system. There exist better naming and routing systems but they are beyond the scope of this project[].
The communication is initial done by means of Java RMI . Java RMI is solely used as a transport medium, not as a distributed OO paradigm. In fact, we can easily replace Java RMI by socket calls, but as a first step the easiness of Java RMI was welcomed. The connections between components will all be passed through the same connections, so component systems are connected with each other and by absorbing a new communication protocol into the component system components will be able to send messages to other remote components without the hassle of compiling stubs, creating proxies, finding them etc etc.
To do this, we will create a component which overtakes the original component and adds some functionalities. This component is called the Portal and manages all outgoing connections as well as all incoming connections. The following sections illustrate how this is done.
The first thing we need to do is to overtake the standard component system. We do this in the Init method.
We have initialised a new, better, component system. Now we need the ability to find other components. This means that all components created in the system needs a global unique name. This can only achieved by overriding the CreateComponent method. The new method will prefix the machine name to every instance. Eg. If component A asks an instantiation of blueprint T, with instance name t, the portal will create an instance cubical:2039/t. The portal does this as follows:
Setting up a connection with another component system can be done by calling the Portal with a ConnectWith call. We thought of making connections automatically, but it turned out that setting up connections is part of error recovery which cannot be implemented, without domain knowledge. So, initiating a connection is left to the user of the Portal.
Everything is in place. We have overridden the component system, we create components with a new name, we can connect with other component systems, now we need to change the meta communication protocol used between components. We will do this by overriding the Undeliverable messages. All messages which are not deliverable will be passed to the component system, since the portal is the new component system, it will receive these Undeliverable messages.
A last thing we need to do is to pass all unhandled message through to the component system. We could do this by rewriting them and forwarding everything, but this is a bit too complicated and absolutely not good. It is not good because another new component system may add extra messages (like for example the portal with its ConnectWith message) which we could not foresee. Therefore we will change the ReceiveMessage a bit. (ReceiveMessage is called whenever a component receives a message, but doesn't have a method to handle it)
We will now illustrate what happens whenever the portal sends something through to another system. Let's say component A has to send message a message M to component B. Component B is on another machine, so message sending will go trough the portals. The msc can be seen in figure 4.1 and 4.2.
The component system has a number of ports. If we want to use the portal component system, we might need an extra infrastructure port. This port notifies all connected components if something happens in the component infrastructure. The component infrastructure is the connection of a number of component systems.
Now, back to error handling. Error handling is application dependent and environment dependent. We aim at systems with a good working TCP/IP network (and TCP/IP stack). Failures which can arise consists of switching of a camera, switching of the network (a router for example) or failure of the central storage. In any case, the application may want to act differently. The controller is the instantiation which will keep track of component systems which should be connected. If something goes wrong, the controller will either queue outgoing messages (if they are important) or throw them away.
Furthermore, the controller will try to set up a connection again as soon as possible. The relevant tasks of the controller are described as follows:
Components can subscribe themselves to retrieve notification of certain new components within the system. For example, a user interface component would like to subscribe to new cameras. The user interface component will receive from the controller a connect message when a new camera joins (or is created).
Disconnecting component can be initiated from anywhere. Every delete command must be send to the controller, which will ask all components to disconnect themselves. If they don't the controller will take action. We will now illustrate connecting and disconnecting
At the moment a component disconnects because there was an error (crash in the component) or a network failure, all dependent components will be notified with a HasDisjoined message. The messages which trigger such an action are
The problems and solutions described in this deliverable, especially regarding distribution issues resembles Java Jini []. Nevertheless we don't want to use Jini because it has started wrong. Jini uses Java (and Java RMI) as a programming paradigm for distributed systems. This is a wrong starting point and makes life hard. We will illustrate this point in the next subsections.
In distributed systems, everything works asynchronously. Nothing works at the same time or rate. There are moments that services need to wait for each other, and there are times things run really concurrently. The amount of time things run synchronous compared to the amount of time where things run concurrently is very small. As such, using a synchronous programming paradigm (like object orientation and Java RMI) is not suitable. We can easily illustrate this with a simple peace of listen code. Suppose we send a message to some remote stub and want to say: 'if you are done calculating, please send me a message back'. If you want to say something like that you start writing code like:
Now, using the same kind of argumentation, I will illustrate how some application can become a client of some Jini service. Like all other examples in this text i have omitted useless try/catch clauses. The example below is taken from []
In this deliverable we have explained the work we've done to refine the component architecture. We have largely enhanced the meta communication protocol used in the component system, and we have illustrated the use of Meta level programming in distributed environments by making the component distributed. We have done this by writing a Portal component and absorb this into the component system.
At the end of the text we have made a provocative point: Java is not suited for programming distributed systems. We have argued that asynchronous message passing is what you want in a distributed system and showed that this is quite hard to implement in Java.
http://werner.yellowcouch.org/ werner@yellowcouch.org |