Ja, mach nur einen Plan Sei nur ein großes Licht! Und mach dann noch ’nen zweiten Plan Geh’ n tun sie beide nicht. — Bertolt Brecht
In this chapter, we describe how theJComp component model, introduced in the previous chapter, is implemented in theJCompframework. TheJCompframe- work is written inJava, and also usesJavaas the host language for the components.
Java is particularly suitable for our purpose, as it is restricted enough to be con-
trollable (e.g., no runtime changes are allowed to the component implementations), yet quite versatile and capable of elaborate reflection. In our implementation, we try to realize the formalJCompcomponent model as closely as possible, in order to retain the properties we have proven for its reconfiguration mechanism. For the ba- sic component framework, two major issues need to be solved: the realization of the message-based communication paradigm, and the prohibition of sharing of mem- ory. The inclusion of reconfiguration capabilities is then rather easy to achieve, but extended features are required for detecting the need for reconfiguration by monitoring the communication and occurrence of errors.
7.1. The Active Object Pattern
The Active Object pattern [LS96] describes how message passing can be achieved by common objects in a language with multi-threading support (such asJava). Basically, the pattern calls for adding a queue to the active object, which conducts a thread switch before the method execution; the caller enqueues amethod request instead of directly invoking the method.
Fig.7.1illustrates the dynamical progression of the Active Object pattern. A client wants to execute a method that is ultimately implemented by a servant, but only visible to the client via a proxy. This proxy builds a method request object that gets enqueued in a queue. If a return value is required, a future is returned to the client. All this is done by the client’s thread. The remainder of the processing now happens in a special thread that belongs to a scheduler. This scheduler is tasked with selecting messages to execute on the servant in such a manner that some abstract criteria (e.g., deadlock-free executability) is met. To this end, the scheduler calls the method request object with a methodguard, which checks the executability. Once this executability is given, the method request object is removed from the queue and dispatched. This is done by invoking callon the method request object, which in turn invokes the requested method on the servant using a Double-Dispatch pattern. Finally, the return value is propagated to the future.
:Client :Proxy :Scheduler :Queue :Servant :M1 m1() enq(newM1) enq(M1) future() guard() deq(M1) dispatch(M1) call() m1() reply to future()
Figure 7.1: Active Object pattern, as described in [LS96]
Basically, the Active Object pattern implements a producer-consumer scheme for method processing: The proxy produces method request objects, which are con- sumed by the scheduler. The pattern refines this idea by detailing how the method request object is to be consumed. Like any producer-consumer example, a buffer is required to transport jobs between the threads; here this buffer is implemented by the queue, although the queue designation is a bit misleading since the Active Object pattern allows for arbitrary choice of the method request to be consumed next. This queue is the only data structure that might be accessed by both threads simultaneously; hence its operations need to be atomic.
A different perspective on the Active Object pattern is given by the observation that it provides message passing between two threads. This view is closer to the intent of the pattern: It makes an objectactive, i.e., has it running in its own thread. By using the queue for communicating the method request to the recipient’s own thread, message passing supporting asynchronous calls is achieved.
7.1.1. Using the Active Object pattern forJCompJCompJComp. The Active Object pat-
tern describes the basic approach towards implementing message passing inJComp, as it is done in the Julia reference implementation of the Fractal component model [BCL+06]. The actual use of the pattern in the JComp framework does not exploit all its power, however. Especially the role of the scheduler, with its
capability to change the message processing order, is not used in JComp. Instead, message execution order is the same as the order of message reception.
Fig.7.2 illustrates the utilization of the Active Object pattern in theJComp framework to support two ways of doing communication: Asynchronous calls, which are not providing return values (we evaluated the use of futures, but have not found a good example [KD99]), and synchronous calls, which block the caller until the return value is obtained. The role of the scheduler and the servant is now merged in the component implementation, which both maintains the queue and finally executes the methods. For synchronous calls, blocking is handled by the proxy. The actual implementation of these ingredients of the Active Object pattern will be discussed in the next section.
7.2. Implementation
TheJComp framework is a fairly slim framework (since the model focuses on only a few features) that is written in theJavaprogramming language, which also serves as the component host language. In using Java, some very benign features are obtained, like dynamic class loading and multi-threading. These are sufficient to allow for wrapping component implementations in a lightweight layer that makes them a component, following the Active Object design pattern as discussed above. On the other hand, Java allows shared memory by using references for objects. Since communication should be able to carry objects as parameters, a conflict arises: By passing references to objects, a covert channel is built – one component might communicate with some other component using the shared memory, and thus bypass any observer. We have discussed in Sect.6.2that sharing memory between components needs to be impeded, and subsequently only included a component- local state in the JComp component model. If the component framework is to represent the component model, we have to avoid memory becoming shared by passing references between components.
7.2.1. Suppressing Shared Memory. TheJCompframework solves this prob-
lem by mandatory serialization of parameters. Technically, all parameter classes need to implement thejava.io.Serializableinterface, since the Javaserializa- tion mechanism is used for obtaining deep copies (i.e., a complete copy without shared parts).
This slows the communication down, but by using static bytecode anal- ysis, the mandatory copying can be suspended for immutable objects (like java.lang.String) that cannot be modified after their initialization. Fig. 7.3 shows the throughput obtained with different communication means. Two com- ponents c1 and c2 call each other i times, using asynchronous calls (synchronous calls would deadlock immediately). As a reference, a direct Java implementation is given (though, for large initial i, the stack overflows). Message passing, which requires building a message request and various calls to listeners described in the next sections, is much slower, of course. Using a primitive Integer variable to represent i is fastest, but using an immutable object that creates a new copy to store the result of the operation i−1 is comparable. Using such an object is more than three times faster than using a generic parameter copying approach, where the parameter is wrapped in an object and i−1 is implemented as a modification to that object’s state.
Still, there is ample room for improvement (we have done some experimenta- tion with static bytecode analysis for detecting immutable objects, which do not have to be copied), and obtaining high efficiency is not a primary concern of the
JCompmodel and framework, but providing a clean communication paradigm im- plementation is; ultimately for obtaining guarantees for reconfiguration. A much
:Client :Proxy :Component :Queue :M1 m1 enq(newM1) enq(M1) deq(M1) call() m1()
(a) Asynchronous call
:Client :Proxy :Component :Queue :M1
m1 enq(newM1) enq(M1) deq(M1) call() m1() r r got result(r) r (b) Synchronous call
c1 c2
p:{m}
p:{m}
(a) Component setup
ζ(c1)(m(i)) =if i >0 then call(p, m,hi−1i)
fi.success
ζ(c2)(m(i)) = call(p, m,hii).success
(b) Component method specification
Javaprimitive: 21,276,595 msg/sec
Primitive data type: 82,562 msg/sec Immutable data type: 69,851 msg/sec Mutable data type: 17,023 msg/sec
(c) Message consumption by various message passing paradigms
Figure 7.3: Message passing cost
bigger problem is given by the fact that it is downright impossible to avoid covert channels withJava. First of all,staticvariables can always be used to transport data between components (if they are on the same virtual machine). Other than that, shared resources like files might breach the explicit communication paradigm. The latter can be mended by utilizing theJavasecurity model and disallowing access to all problematic resources. Prohibiting access to static variables, on the other hand, would require bytecode analysis, which is prone to disallowing legiti- mate behavior if the analysis is not exact enough – e.g., a read access of Math.PI should not be prohibited, but a read access to Singleton.INSTANCE should be prevented if – and, preferably, only if – the class Singletonoffers mutator meth- ods. It is quite unclear if read access to System.outcan be allowed. TheJComp framework does not prohibit static variable access; instead, static variables act as a substitute for what is known as Utility Interfaces in SOFA [BHP07]; we will see some examples in Sect. 7.3.4and Sect.8.3.3. For a completeJava-based com- ponent framework that enforces explicit communication, however, static variable access needs to be accounted for.
7.2.2. Role and Communication Implementation. Implementing the com-
munication is greatly facilitated by the fact thatJava uses interfaces in quite the same fashion as we use them for roles: As sets of method declarations. Hence, it is straightforward to have the Java implementation of a component be a class that implements the provided interfaces and has attributes that represent the required roles.
Component interfaces are implemented by theirJavacounterparts. All neces- sary information is included, except the message synchronicity. We therefore pro- vide two annotations@AsynchronousCalland@SynchronousCallthat are used to annotate a method’s call type. To allow for consistent interpretation of method calls, some constraints need to be obeyed:
• If a method is declared to by asynchronous, the return type must bevoid and no exceptions can be declared,
• all parameter types and, for synchronous non-void methods, the return types must either be primitive types or implement java.io.Serializable.
A component is implemented in a Java class that extends jcomp.core.AbstractComponent. It has to implement the interfaces it pro- vides and implement their methods. Only two elements within such a component class need annotation: The roles and the component parameters. Roles are declared by adding@RequiredInterfaceannotations to attributes. The annotation can be provided with a role name, but if this is omitted, the name of theJavaattribute is used. Furthermore, it is not required to provide a setter method for the attribute or grant a visibility beyond private – the JComp framework utilizes a security manager that allows the direct setting of the attribute. Avoiding redundant information (e.g., maintaining a role name both in the annotation and the Java attribute) and requiring as little as possible from the component implementation code helps to keep the component applications slim and easy to understand.
Roles can also be given a scope, which can be either permanent or temporary, with permanent being the default. Temporary roles are used for transferring data during reconfiguration as described in Sect. 6.8.1. Given an annotated attribute that is declared to be a permanent role, the JComp framework needs to provide a link to a target component as specified by the component setup. This is done at component instantiation time; a proxy is used to translate the Java method invocation to the message passing as prescribed by the JComp communication model. In JComp, this proxy is instantiated from code that is generated for the required interface using the Velocityengine [Apa08]1.
Once invoked by having the component implementation call a method on the role’s attribute, the proxy builds a method request object that stores the method call name, deep copies of the parameter objects, and information required for calculating statistics and performing reconfiguration. This method request ob- ject is now passed to the target component by invoking a method defined in jcomp.core.AbstractComponentwhich enqueues this method request in the queue of the target component. Eventually, this method object will be dequeued and ex- ecuted, by calling a method performthat invokes the appropriate method on the target component in a scheme similar to double dispatch.
If the method call is synchronous, the method request waits for completion of the method and stores the result – if any; the result might either be a value or an exception. The proxy object, itself waiting for the target method’s completion, maintains aJava-provided monitor (obtained by invoking Object.wait()) on the method call object, and is notified on the return value’s setting. This communicates the method call completion back to the caller component.
7.2.3. Defining and Launching Component Setups. Building a component
setup with theJCompframework is basically done in three steps: (1) instantiation of ajcomp.assembly.Assemblyinstance,
(2) declaring the component graph by calling methods of the Assembly in- stance,
(3) establishing the components and launching them by calling start() on theAssemblyinstance.
1Note that
Java1.6 provides thejava.lang.reflect.Proxyclass that facilitates the same approach with minimal overhead, but this was not known to the author, who was very influenced by code generation done by some well-known model checking software at that time.
getComponentForClass(String id, Class c)
build a component descriptor for a user-supplied class deriving AbstractComponentand a unique, user-supplied ID
getComponentForClass(Class c) build a component descriptor for a user-supplied class deriving
AbstractComponent, with a generated ID
addComponent(ComponentDescriptor c, ComponentParameter[] p)
add a component to the network, with user-supplied parameter settings addComponent(ComponentDescriptor
c)
add a component described by a component descriptor to the network, with an empty parameters set linkComponents(ComponentDescriptor
src, ComponentDescriptor tgt, String role)
link the role of componentsrcto componenttgt
Table 7.1: Component network creation commands in JComp, provided by the class jcomp.assembly.Assembly
The Assembly instance maintains a graph representation of the compo- nent setup. The basic methods provided for establishing a component net- work are shown in Tab. 7.1. The reference to components is given by ComponentDescriptor instances, and they can be obtained from the assembly by calls to getComponentForClass. Since these descriptors will also be required for planning reconfiguration, they need to be added to the component setup by a dis- tinct call toaddComponent. This call also takes an array of component parameter which are essentially name-value-pairs and which are to be injected into the com- ponent before it is launched. Parameters are useful to obtain a higher diversity of components [OLKM00], in theJCompcomponent model, they can be represented by a read-only part of the data state that is set by ι. Calls to linkComponents are used to establish the connections of roles to other components. The assembly also provides methods for adding the various listeners described in Sect.7.2.5, for obtaining the component graph for inspection, and for preparing and executing reconfigurations.
Once the setup is complete, the entire system is started by a call to Assembly.start(). First, the assembly checks the well- and completely- connectedness of the component setup, and checks the various implementation- related constraints on the components and interfaces (e.g., no asynchronous method must have a return type other thanvoiddeclared). The assembly then builds the re- quired code for the communication proxies and instantiates the actual components, injecting the communication endpoints and finally starting each component’s own thread. Finally, all components providing theMainInterface, which consists of a single asynchronous method start(), get a start method object added to their queue. Those components are called initial components; their processing of the start method launches the component application.
7.2.4. An Example. We present a small producer-consumer example to illustrate
how a component setup is realized in theJCompframework. The component setup is illustrated in Fig. 7.4. It consists of two components: cc and sc – a “client” component (that produces tasks) and a “store” component (that stores data). For
cc sc self :MainInterface
store :StoreInterface
Figure 7.4: Component setup for the producer/consumer example
defining this example, we require two interfaces: the MainInterface which we introduced in the last section, and an interface StoreInterface that contains a single asynchronous method store(String s).
We can thus define the components
C= (cc,{MainInterface},{self7→MainInterface,
store7→StoreInterface}, µ(C),{i7→0}) and
S = (sc,{StoreInterface},{}, µ(S),{entries7→ hi}).
The method evaluators are defined as
µ(C)(start) = (i←i+ 1).call(store,store,h”i”i).
call(self,main,hi).success and
µ(S)(store(s)) = (entries←entries::s).success The component setup is formally defined as
({C, S},{C7→ {self7→C,store7→S}, S7→∅},(C,start)).
Implementing these elements inJava is straightforward. First, we implement theStoreInterfaceinterface, which is a regularJava interface with annotations used to describe how communication is conducted:
1 public interface StoreInterface { 2 @AsynchronousCall
3 public void store(String s);
4 }
Writing the components is equally straightforward:
1 public class ClientComponent extends AbstractComponent
2 implements MainInterface {
3 @RequiredInterface
4 private StoreInterface store; 5 @RequiredInterface
6 private MainInterface self; 7
8 private int i; 9
10 @Override
11 public void start() {
12 i++;
13 store.store(Integer.toString(i)); 14 self.start();
15 }
16 }
1 public class StoreComponent extends AbstractComponent
2 implements StoreInterface {
3 private LinkedList<String> entries = new LinkedList<String>(); 4
6 entries.add(s);
7 }
8 }
Note that these two components are actually component types that need to be instantiated; as we discussed in Sect. 2.3, a distinction is usually omitted.
For building the component setup and starting the application, a regularJava main method is used. The component setup is built by invoking the methods presented in Tab. 7.1 (or, rather, shortcuts that omit unnecessary parameters) as described in Sect. 7.2.3:
1 public static void main(String[] args) { 2 a = new Assembly(); 3 cc = a.addComponent(a.getComponentForClass(ClientComponent.class)); 4 sc = a.addComponent(a.getComponentForClass(StoreComponent.class)); 5 a.linkComponents(cc, sc, "store"); 6 a.linkComponents(cc, cc, "self"); 7 a.start(); 8 }
ccandscare static variables; we will later use them for building a reconfiguration plan. The JComp framework implementation supports the loading of component setups from graph files, but we usually build component setups just as shown, by directly calling the appropriate methods on the assembly.
7.2.5. Monitoring Facilities. The JComp framework needs to support a vari-
ety of monitoring facilities in order to allow for the triggering of reconfiguration, as we will discuss in Sect. 8.1.2. While monitoring can be added to most systems externally [Sca00] or at component code level by introducing filter components