APAM/DYNAMAN

From Wiki Adele Team
Jump to: navigation, search

Introduction

Apam is a platform designed for the support of a number of independent applications using and sharing services and devices in a context at least partially unpredictable. The platform does its best to simplify the design and development of such applications by automating the selection and connection between services, by handling the dynamic devices and services, by managing the application coordination and conflicts. To a large extent, the platform automatically adapts the applications to the current context, within the range of admissible behavior as expressed in the metadata.
In the following we detail how the platform manages the Apam service-based applications; its default behavior, and the primitives allowing changing the default behavior. Implementations and specification use the same name space (because they are both packaged as bundles, and Maven and OBR do not make the difference). “Component” means either implementation or specification. Let us declare the following syntax fragments (bold italic are literals):

depSource  == specification=”xx” | implementation=”xx” | component=”xx”
dependency == depSource id=”ddd”
targetDef  == <resource name=”xx”/> | 
              <resource name=”xx”> constraints </resource>
resource == component | specification | implementation |interface | message 
constraints== <contraints> {<constraint filter=”xx”/>} </contraints> |
               <preferences>{<preference filter=”xx”/>} </preferences>

Basically, Dynaman records which the expected dependencies are. When an instance appears (dynamically or explicitly instantiated), and after that instance has been placed inside a composite, Dynaman checks which expected dependency can be satisfied by the new instance.

An example scenario

In order to illustrate, let us assume the following declarations:

<specification name=heaterControl interface=HeaterControl>
	<properties>
		<property exclusive=true/>
	</property>
</specification>
<specification name==thermometer interface=”Thermometer” />

<specification name=”energyControl” interface=”EnergyControl”>
	<dependency>
		<specification name=”heaterControl” />
		<specification name=thermometer id=temp>
                     <constraints>
                         <instance filter=”(location != oven”) />
                     </constraints>
                </specification>
	</dependency>
</specification>

<implementation name=”energyImpl” specification=”energyControl”   
      className=”energyMain”>
	<specification name=”heaterControl” field=”heater”>
	<specification name=”thermometer” field=”thermometers”>
</implementation>
public class energyMain implements EnergyControl {
(1)	HeaterControl heater ;
(2)	Set<Thermometer> thermometers ;
	
(3)   heater.method () ;
(4)   for (Thermometer temp : thermometers) {  }

Lazy and Dynamic bindings

The variables mentioned in the “field” attributes of the meta data (heater and thermometers in our example) are called “instrumented variable”; it means that the value of these variables will be completely managed, transparently by Apam. In the example above, heater is an instrumented variable, it is not initialized in the code, but line 3 will work because Apam will transparently set the address of a heater in the heater variable. Most of the following are variations around the moment and the way instrumented variables are managed.

By default, Apam is lazy and dynamic. Apam is lazy because it is only when executing line (3) for the first time that the heaterControl dependency is resolved. If the resolution fails, heater will be null. If the heaterControl disappears during execution, variable heater will become null, and another resolution will be attempted at each access to the heater variable. If the new resolution succeeds, the heater changes transparently during execution. It is therefore strongly advised not to copy variable heater into other variables or to give it as parameter. Setting variable heater manually in the program is ignored (for example, writing “heater = null;” in the program does nothing).
Similarly, the thermometers set will be resolved only when executing line (4) for the first time; it will contain all the thermometers satisfying the constraints at time of the resolution, or the empty set of none are found. Then, if a new thermometer appears, or if a thermometer disappears, Apam creates a new set containing the current set of thermometers, and changes the variable thermometers toward that new set. Therefore, line (4) is risky if the set changes during the loop. It is advised, if these dependencies can be dynamic, to write our example as follows:

(3)   if (heater != null) heater.method () ;
(4)   Set<Thermometer>tempLoop = new HashSet<Thermometer>(thermometers);
(5)   for (tempLoop temp : thermometers) {  }

Note that, even with these precautions, still some abnormal behavior may appear, if an instrumented variable is changed by Apam while a thread is using that variable. If a new thermometer appears, all the multiple dependencies toward Thermometer are updated. For example, if more than one instance of energyMain exist, the system will try to update all the variables thermometers adding the same Thermometer instance in each set, which may fail if the thermometer instance is not sharable.

Action own

An instance is created in Apam either because a) it “appears” (a device is discovered or it is created by a third party), or b) it is created by Apam to resolve a dependency. In case b), the new instance is owned by the composite containing the source of the resolved dependency. In case a), the instance that appears in the system has no owner; it is created in the special “unused” composite and nothing else occurs. An unused instance is visible by everyone, and may be solution of a future resolution; in which case it is moved into the composite containing the client. The ready policies indicate which action can be undertaking when a new instance appears. The “owns” primitive allows to define which composite will be the owner of a service that appears.

 
<owns depSource/>
<owns depSource>
    constraints
</owns>

The first sentence expresses that if an instance appears, which is resolution of component (either specification or implementation) with name ”xx”, its implementation is placed inside the current composite type, and the instance is placed inside the current composite instance, without client. The second sentence expresses the same, but the appearing instance must also satisfy the constraints. The constraints only apply on component “xx” properties. The system tries to check that a single “owns” clause in the whole system can be true at any point in time (ownership should never be ambiguous). This property can be statically checked only if the constraints are simple expressions over values of the same enumerated attributes defined in the component. For example, if the composite Comfort must be sure it will contain the Thermometers, it should indicate:

 
<owns component=”Thermometer”/>

It means that Comfort will own all the thermometers. A composite Kitchen can indicate:

 
<owns component=”Thermometer”> 
   <constraints>
      <constraint filter=”(location=oven)” />
   </constraints>
</owns>

This example expresses that the Kitchen composite must own the oven thermometer. In this example, the system will check the owns clauses and detects that the Thermometer ownership conflicts between Comfort and Kitchen composites (the oven thermometer cannot pertain to both the Comfort and the kitchen composites). If both the comfort and the kitchen composites are in composite Home, this error is detected when building Home, otherwise it will be detected when deploying the Comfort or the kitchen composite, and the deployment fails.
The ownership of a device allows the owner composite to define the visibility, sharing and access conflict resolution related to that service. The “owns” primitive is executed before any start, since visibility depends on the composite owner.
Note that the owns primitive is applied only on instances that appear, not on those instances created by Apam during a dependency resolution. Better say, owns is applied on devices and on those services that are instantiated by third parties.

Action instance

 
<instance depSource [name=”ii”]/> 
<instance depSource [name=”ii”]> 
     [targetDef] [properties] [dependencies] <! Condition for creation -->
</instance>

This is an extension of the instance tag. It asks for the creation of an instance called ii of depSource. If targetDef is missing, the instance creation is performed as soon as the composite containing this action is started; otherwise the instance is created as soon as a visible instance satisfying targetDef is created.
If the instance action is defined in an implementation, depSource is resolved as a root composite, otherwise depSource is resolved in the scope of the composite type which defined this model, and inside one (arbitrarily selected if more than one) of its composite instances. An instance of component is immediately created and therefore may be solution of subsequent resolutions.
If the component implements the ApamComponent interface, the created instance gets the control immediately. If no “own” primitive exists for that instance, it will be created in the root composite and will become the main instance of a default composite instance.
The system does not make any hypothesis about the relationship between the instance targetDef and the instance created. In particular, the ownership of the instance that appeared, and its future availability are not granted.

 
<instance component=”energyControl” /> 
<instance component=”energyControl”>
   <specification name=”Thermometer” >
      <constraints>
	   <constraint filter=”(location=living)” />
      </constraints>
   </specification>
</instance>

In the first example, an instance of component energyControl is created as soon as the component containing that sentence is started. In the second case, the energyControl is launched as soon as an instance of specification Thermometer satisfying location=living is discovered.

Dependency properties

When an instance disappears, all the wires (the dependencies) leading to that instance are removed. For a single dependency, the variable is set to null; for multiple dependencies, to the empty set. Then the system behaves exactly as if that dependency had never been resolved before. Therefore, a new tentative resolution will be executed the next time the dependency will be called, if successful, the client will transparently use another provider; if it fails, by default null is returned, and the client has to test the pointer value in order to avoid a “null pointer exception”.

Property wait

When a resolution fails, the dependency is said to be “frozen” and the current thread and all subsequent thread using this dependency will be halted and queued. When an instance that satisfies the dependency appears, the dependency is resolved and the blocked threads are resumed. Note that the dependency only is frozen, any thread entering the component object without using the frozen dependency will perform as usual. An implementation with a wait (and delete) dependency is visible for a client only if all its wait dependencies can be resolved for that client.

<wait dependency />
<wait specification=”energyControler” id=”temp”>

If all the temp dependencies are removed (no thermometer is available), the system will halt all threads making use of the Thermometer dependency, and will resume them after at least one Thermometer dependency has been established (at least one thermometer is available). The energyControler cannot be instantiated if no thermometers are available.

Property delete

<delete dependency />

When a dependency disappears (the provider disappeared, and no alternative provider could be found; for example, the last thermometer disappeared), the client instance must be deleted. All the wires toward the deleted instance are removed . Deleting an instance x is interpreted by the system as “instance x disappeared”. Note that Apam turns to “null” or the empty set all the variables related to the deleted dependencies (variable heater in our example), but if the program contains copies of these variables, the Java VM will not remove the corresponding Java objects, and errors may occur if using these variables.
If a thread was inside instance x at the time it is deleted, the thread continues its execution, until it leaves x normally, or it makes an exception. No other thread can enter a deleted object since wires have been removed. An implementation with a delete dependency D is invisible from a client C if dependency D cannot be satisfied for C .

<delete specification=”energyControl” id=”heaterControl”>

The energyControl instance has to be deleted if the heaterControl dependency disappears. As long as heaterControl cannot be resolved, no energyControl instance can be created.


Property mandatory

<mandatory dependency />

By default, a resolution is lazy, and may fail, leaving the variable with a “null” value or an empty Collection which requires the programmer to test the variable before any use. Many components can perform their job only if some dependencies are available; it makes no sense starting a component without these dependencies. We call such a dependency mandatory; a component with a mandatory dependency is called a coupled component, and the provider of a mandatory dependency is called a coupled provider.
The life cycle of a coupled component is completely linked (coupled) with the life cycle of its coupled providers. A coupled component is created with its coupled providers, and deleted if at least one of its mandatory dependencies cannot be satisfied (at least one of its coupled providers disappeared and could not be replaced). Note that a chain of coupled components is created atomically, but chains are not statically defined, since each mandatory dependency is resolved independently; the selected component is not statically known (in general); it depends on the current context and can be itself a coupled component or not. A mandatory dependency is a delete dependency. Therefore, if a coupled provider disappears, Apam tries to satisfy the mandatory dependency with another service, but if no alternative solution is found, the coupled client is deleted. The coupled provider is not visible as long as at least one of its delete dependencies cannot be satisfied. This algorithm ensures that the resolution process will not loop, and will be optimized. If the deleted client is itself a coupled provider, the same process is repeated one “client backward”, until an alternative resolution is found or there are no more mandatory dependencies. Therefore coupled chains are deleted only after having tried all the alternative solutions at each level of the chain.
The resolution mechanism is such that the coupled component instances that cannot be created (some coupled providers are missing) are simply ignored . For example, a component with a mandatory dependency toward a device will be ignored as long as this device is missing, and automatically becomes selectable as soon as the coupled device appears.

<mandatory specification=”energyControler” id=”temp” />

It means that the component energyControler can be started only if its dependency called temp can be immediately satisfied, i.e. if at least a thermometer (except oven) is available, and will be deleted as soon as all thermometers disappear.
Access conflict resolution. Policy change and composite state When a client has established a wire towards a provider, this wire is “permanent” until the provider disappears; however in some cases, there is a need to change or delete an existing wire. Composites, specification as well as instance, can declare a state, which is a special property with name “state” and with enumerated values. For example:

<specification name=”energyControl” interface=”EnergyControl”>
   <composite>
      <states values=”[Ok, vacation, emergency, night]” default=”normal”/>

Actions grant and release must be defined with respect to the state of the current composite .

Action grant

<grant dependency when />
when == when=“state” | when=“( {state ,} )”

<grant specification=”emergencyControl” id=”heaterControl”  
                                   when=”emergency”/>

The component emergencyControl preempts the service matching heaterControl dependency if the composite state instance is emergency AND if the emergencyControl and heaterControl instances are “owned” by the current composite instance. It means that if a wire exists toward heaterControl, it is removed and recreated from emergencyControl. In orther words, emergencyControl takes the control of the heater in case of emergency.
The client on which the wire is deleted is automatically placed in the “wait” status for that dependency. As soon as the when condition is no longer satisfied (the current composite instance state changed), the granted wire is removed; and the waiting component, if any can be resumed.

The system checks that at most one grant condition can be true on the same instance.

If we also have

<grant specification=”energyControler” id= HeaterControl”
	when=”{Ok, vacation, night}” />

Because of the emergencyControler “ grant” expression, the energyControler loses the door control in case of emergency and waits, but regains that control as soon as (state=Ok). The energyControler and emergencyControler will get the heater control depending on the state value.

Action release

When a client has established a wire towards a provider, this wire is “permanent”; only the client termination, or a “grant” clause can remove the wire. When the provider is an exclusive or limited resource, it may be wise to release the provider as soon as possible. This is what performs the “release” clause.

<release dependency when />
when == when=“state” | when=“( {state ,} )”

Example :

<release specification=”emergencyControl” id=”HeaterControl”
	when=”{Ok, vacation, night}” />

It is possible to change the binding explicitly.