Constructing Interoperable Components in Distributed Systems

0 downloads 0 Views 96KB Size Report
annotations into Java programs adding Eiffel-style assertions and other .... B specification is not object-oriented and Abrial [1] chose to present the lift as a single ..... test cases that will exercise the interactions between components in a ...
Constructing Interoperable Components in Distributed Systems Sea Lingy , Heinz Schmidty and Rohan Fletcheryy y School of Computer Science and Software Engineering yy Co-operative Research Centre for Enterprise Distributed Systems Technology Monash University, Victoria 3145, Australia fsling,hws,[email protected] Abstract The separation of architecture definition language (ADL) and interface definition language (IDL) is widely accepted in the distributed system community. It separates issues of hierarchical composition and configuration from those of services, communication and synchronisation and thus eases reconfiguration without changing the interfaces and implementation and allows the change of services within well-defined configuration constraints. Our approach towards distributed system definition draws on the Darwin project’s notion of distributed system architecture definition and on Eiffel’s notion of design-by-contract. We extend contracts to deal with synchronisation and coordination at a higher level, explicitly targeting both large-scale loosely-coupled distributed components and tightly coupled parallel or multithreaded objects. In this paper, we present a Java binding of the resulting contract extensions. We incorporate annotations into Java programs adding Eiffel-style assertions and other contractual constraints. Java component code and constraints are then associated to architectural diagrams in a visual programming environment. The paper illustrates the language features in a case study of a lift control system. Based on the case study, the component-based design approach is illustrated and the internal synchronisation of method execution is explained.

1. Introduction

A distributed system is generally composed of autonomous and heterogeneous components. Components are large-grain objects consisting of other auxiliary objects. Components are ‘not just’ objects. Distributed components are relatively autonomous, interoperable and fault-tolerant. They are usually associated with some protection domain imposing access constraints and fairly narrowly defined service contracts, often subject to reliability requirements. The separation of architecture definition language (ADL) and interface definition language (IDL) is now widely accepted in the distributed systems community. In DARWIN [5] for example, the architecture definition includes hierarchical composition, bindings and various configuration constraints. In contrast, the interface definitions are associated with bindings, and have formalised some aspects of their concurrent communication, beyond the service signatures of components Furthermore, like Szyperski [17], we view components as deployable. This means that they are available in some executable form, but their design or source code may not. Such deployable  The work reported in this paper has been funded in part by the Co-operative Research Centre Program through the

Department of Industry, Science & Tourism of the Commonwealth Government of Australia.

distributed components must be replaceable, possibly while other components depend on them. As pointed out by Schmidt [12], the existing interoperability platforms do not meet a number of requirements imposed by the integration of objects, distribution and concurrency. Most current interface definition languages (IDL) do not model anything beyond signatures, i.e. the names of service functions and the types of their parameters and results. Moreover, they generally assume a sequential component model, in which requests are served one at a time. In our approach, we extend Meyer’s notion of object contracts to contracts for distributed components. Initially, we proposed a type and reasoning system incorporating concurrency into contracts [13, 16] with a binding of these concepts to the Sather language. The concepts extend Eiffel’s minimalist concurrency [7, 8] by providing a semantics for multithreaded components and the synchronisation of multiple requests processed in parallel. These concepts have been refined to allow synchronisation contracts almost as “add-ons” to existing software and also to provide a Java binding of the concepts. They are supported in a graphical programming environment, based on the Software Architect’s Assistant [9] from the Darwin project. In this paper, we show how to model a lift control system in our framework. The Java language binding is used to specify the Eiffel-like assertions and synchronisation constraints that exist in the system components. The rationale behind the assertional language features is explained and the object synchronisation policy adopted is illustrated, based on the example case study. The paper first provides an overview of the underlying abstractions. Then we describe our design tool briefly. This is followed by an account of the lift control system case study. Finally, we elaborate on some details of the Java binding. 2. Overview: concepts and tools

In [12] we have formalised the separation of architecture, interface and computation by way of three different kinds of classes: Kens are self-contained, coarse-grain components hierarchically composed from other kens. If they contain other kens, they are composite, else we call them primitive. Kens deal with configuration, for example, composition and interconnection. Their assertions define configuration constraints. Their methods partially reconfigure a system. Figure 1 depicts a composite ken while its component kens are shown in Figure 2. In general, kens are autonomous multithreaded components. Gates protect kens, which cannot be directly accessed. All calls, all data communicated and all objects migrated to a ken must come through gates. The directed connections from a ken’s gate to another ken’s gate reflect the use relation. Gates are themselves objects. Their methods serve communication and coordination; their assertions define synchronisation constraints. The notion of having interface objects rather than just interface abstraction or contract leads to a semantically rich notion of wrappers and adaptors in the form of gates. Not only does it allow the designers to protect existing functionality within the wrappers, but also it permits the replacement of existing components behind the gates and walls, according to a set of well-defined services they provide to the outside. Citizens represent those classes realising the solution to the given programming problem in detail. The anthropomorphic analogy to organised distributed communities of citizens is deliberate.  The Oxford dictionary defines ‘ken’ as a region or area of knowledge. The Japanese word ‘ken’ means a local community (of villages) or a township.

2.1. Distributed architecture: kens Our methodology is supported in an extension of the Software Architect Assistant (SAA) of the Darwin project at the Imperial College [9] written in Java. Figure 1 shows the user interface of the tool with a snapshot of aspects of the lift control system with a composite ken liftSystem.

Figure 1. The lift system The main window contains a scrolling pane where our system components are drawn. The menu and tool bars across the top of the screen allow the designer to construct component models using our graphical notations. Down the right-hand side of the screen, lists of kens and gates are available for reuse and instantiation in new configurations. To view the internals of a ken, the designer selects the ken using the mouse and/or keyboard ‘zooming’ into a refinement configuration. Figure 2 shows such a refinement of the liftSystem.

Figure 2. Primitive kens of the lift system

This consists of several component kens, whose gates make them accessible to the outside with proxies connected to gates of neighbouring kens. In our system, while kens are modelled as blue bordered rectangles, gates are modelled as red triangles that point inward to a ken and proxies are modelled as green triangles that point outward from a ken. In the figure, gates are shown with a darker shade than proxies. Proxies are connected to gates by a black line. Such ‘horizontal’ interconnections are called bindings. There is a kind of virtual and ‘vertical’ interconnection, which maps high-level gates to lowerlevel gates or alternatively lower-level proxies to higher-level proxies – and thus defers binding to a higher level. In such mappings, the gates and proxies of the surrounding ken are represented as red and green boxes respectively. For example, in Figure 3, which shows the composition of a lift, the rectangles on the left column are gates (with a darker shade) and proxies (with a lighter shade) from the higher level lift component. To access the textual description, i.e., class description, of a component, the designer clicks the right mouse button over a ken, gate or proxy. This will display a dialog box with an edit button, which executes Emacs or any other editor selected according to the user profile. 2.2. Distributed interfaces: gates The lift control system is selected as a case study because it is a reactive system with interoperable, distributed and replicating components. There are numerous elaborations of the lift in the literature. We try to follow that presented with the B method [1] which uses contracts. However, the resulting B specification is not object-oriented and Abrial [1] chose to present the lift as a single centralised controller. However its operations are axiomatised using pre- and post-conditions and the invariants. To suit our study, the problem has been re-modelled in the component-based fashion and the operations and their respective assertions associated with appropriate separate components. The assertions are presented as Java boolean expressions, preceded by new keywords. While Abrial [1] has specified an arbitrary number of lifts installed in a building with an arbitrary number of floors, for simplicity we model two lifts and three floors in our case study.

Figure 3. Composition of a lift The liftSystem is the top level ken, as shown in Figure 1. The simple, but non-trivial ken

consists of two lifts (lift1 and lift2), serving three floors (floor1, floor2 and floor3), as shown in Figure 2. Both lifts converse with each other, and each lift also communicates with every floor through appropriate proxies and gates. Each lift component can be further decomposed into the components shown in Figure 3. The six rectangles on the left are in fact the interface gates and interface proxies of the lift (lift1 or lift2) component. They are mapped to the component gates and the component proxies of controller respectively. This differs from the notion of binding, which binds a proxy of one ken (say, lift1) to some gate of a different ken (say, floor1), as shown in Figure 2. In Figure 3, controller and motor are the basic kens, which means that they cannot be further decomposed into other citizens. liftPanel is a composite ken containing lift buttons as the citizens. All methods pertaining to the lift operations are found in the class, defining the basic controller ken. The following shows a fraction of the LController class definition, written in a Java like notation: holds int floor == end#continueUp - end#continueDown + 1; void requestLift(int flr, int num) requires (flr = bottom) { } void continueUp() awaits up & stop & !out(floor) & ... holds self ensures floor’ > floor { } void continueDown() awaits down & stop & !out(floor) & .. holds self ensures floor’ < floor { } invariant invAtBottom: (floor == bottom) ? up : true ; invariant invAtTop: (floor == top) ? down : true ;

Clearly, the main difference between the above Java extension and standard Java code is the inclusion of various Eiffel-style contractual assertions, preceded by keywords such as invariant, holds, requires, awaits and ensures. It should also be noted that the body of every method in the class definition is left empty because the focus, in this paper, is on the synchronisation aspects of the components with respect to the entire system using the new language features. It is expected that the body will be implemented in standard Java code. The assertions for a method always precede the method body. In the usual sense, they bind the method caller and callee by precise definitions of obligations and benefits, i.e., design-by-contract [7]. Similar in essence to Eiffel, pre-conditions are introduced by requires, post-conditions by ensures and the assertions introduced by invariant applies to all methods, being in effect added to both the pre-conditions and post-conditions. For synchronisation purposes in a distributed environment, other syntactic constructs, such as holds and awaits, are introduced in the language. The use of these keywords will be discussed in greater detail in the next section.

2.3. Implementation and synthesis Our tools synthesise code from the architectural and interface definitions and combine this with the user provided code for citizens. The code for a composite ken, such as the Lift component shown in Fig. 3, might include the following code representing the construction, binding and reconfiguration of such a system. class Lift { LController c; Motor m; LiftPanel lp; Lift() { // Creating citizen components c = new LController(); lP = new LiftPanel(); m = new Motor(); // Binding the citizens. c.bind_outConToB1(lP.find_inConToB1()); c.bind_outConToB2(lP.find_inConToB2()); c.bind_outConToB3(lP.find_inConToB3()); lP.bind_outB1ToCon(c.find_inB1ToCon()); lP.bind_outB2ToCon(c.find_inB2ToCon()); lP.bind_outB3ToCon(c.find_inB3ToCon()); c.bind_outConToM(m.find_inConToM()); } // Gate configuration methods void bind_outltof1(InterfaceGate x) { c.bind_outltof1(x); } void bind_outltof2(InterfaceGate x) { c.bind_outltof2(x); } void bind_outltof3(InterfaceGate x) { c.bind_outltof3(x); } InterfaceGate find_inf1tol() { c.find_inf1tol(); } InterfaceGate find_inf2tol() { c.find_inf2tol(); } InterfaceGate find_inf3tol() { c.find_inf3tol(); } ... }

Our component model permits multiple subtyping and inheritance for kens and gates. In this respect the architecture tool differs considerably from that of the DARWIN project, in which components are object based, i.e., provide encapsulation but no inheritance and subtyping at all. In particular, bindings and mappings allow subtyping, provided the effective gate accessed eventually via a proxy-gate chain conforms to the contractual requirements provided to the using ken by the proxy. In our Java binding, we emulate multiple inheritance by delegation. 3. Synchronisation Constraints

Our contract model for distributed components include synchronisation constraints. The specification of synchronisation has raised numerous problems in concurrent object-oriented programming languages. It has been well-documented that the phenomenon called the inheritance anomaly [6] poses a difficulty specific to concurrent objects. The inheritance anomaly occurs when “synchronisation code cannot be effectively inherited without nontrivial class re-definitions”. There are many proposals [2, 3, 8, 11] made to solve the problem in the literature. A common ground in most of these

solutions appears to be the separation of synchronisation elements from the implementation code. We share a similar viewpoint and have shown elsewhere (see e.g., [13, 16]) that this blends with the semantic notion of inheritance: subtyping and conformance. For our Java binding specifically, we note that Java combines multi-threaded programming with single inheritance of code, and multiple subtyping of interfaces. However, it lacks a rigorous notion of concurrent interface specification and component compatibility, which is crucial for the construction of evolving component-based systems, and is necessary for the replacement and extension of existing components. Therefore, Eiffel-style assertions are introduced in our Java binding in a Java like notation for distributed contracts. The synchronisation constraints expose synchronisation and behaviour abstractions as part of the gate interface definitions. We assume that the methods themselves will not in general contain synchronisation code. To obtain maximal reuse, it should then be possible to inherit the same method code both in sequential and concurrent execution contexts. In a way, the synchronisation constraints are thus annotations to method signatures separate from the body itself. This separation also means that in a subclass, the synchronisation policy may change without changing method bodies, or method implementations change without changing the synchronisation of methods. For a method to execute, all its guard clauses must be true and all relevant objects must be held exclusively. The corresponding test of the method guards and the availability of objects to be held and their acquisition is atomic. This atomicity implies that guards can be protected from changing between the time of test and the time of entry to the method body (by holding corresponding objects). The holds keyword in connection with other entities in the current class relates to this mutual exclusion synchronisation constraint. For example, a method excluding other methods of this class, will hold self and thus will exclude other methods. A formal parameter declared as holds, is already held by the caller. Instance variables declared as holds are defined by atomic synchronisation expressions based on event counters such as begin# and end# of methods. As objects are multithreaded by default, interference of acquiring objects to hold and successful evaluation of guards form one atomic event. The method execution atomicity and its resulting states are modelled in Figure 4, using a simplified Petri net notation [10]. Describing the figure briefly, when a method is called, the call event occurs and the method is pending, while awaiting execution. At the same time, due to the method request, a request counter is incremented, represented by the creation of a token (black dot) in req#. To schedule the method for execution and hold the required objects, all guards must be satisfied and all the n objects must be available, while the execution is pending. In other words, the places (circles) representing these conditions must be satisfied (filled with tokens) before the event Schedule Method & Hold Objects can occur. Note that the dashed input arcs model the notion that tokens are returned to the places as soon as the event occurs, ensuring that the guards remain true just before the execution of the method body. Objects obj 1: : :n are also held exclusively without external interference during the execution. At the end of the execution, the method returns and objects are released becoming available again, while the counter end# representing the end of method call is incremented. A more detailed discussion of the different phases of method execution follows. 3.1. True guards and object availability When a method is called it is pending until all its guards are true and all objects to be held exclusively are available. When this is the case, it is scheduled for execution and the body is then entered as a result. Furthermore, all pre-conditions (requires) and invariant clauses must be true

req# Method Call

n objects to be held

external influences set of guards

obj 1

Time Line

obj 2

obj n

Pending

Available

begin# Method Schedule & Hold Objects

obj 1

obj 2

obj n

Executing

Held

end# Method Returns & Release Objects

obj 1

obj 2

obj n

Available

Figure 4. States of method execution and objects held as a matter of correctness at the point. In Meyer’s proposal [8], which relies on purely sequential object semantics, pre-conditions become guards in separate methods and they become assertions in non-separate method. As such, there is an overloading of the keyword requires. In our work, to accommodate the multithreaded semantics of kens, all guards are explicitly marked by the awaits keyword while requires remains reserved for assertions. For example, in the requestLift method from the previous section, the requires pre-condition states that the required formal parameter must be one of the three floors. An exception will be thrown if this condition is not met. On the other hand, assertions or guards preceded by awaits employ a waiting semantics for the method before execution. Figure 4 shows the set of guards being true, each with a token residing. However, the decision to start executing a method must be made as a single atomic unit. This ensures that all guards must be true and all objects be available simultaneously. It is well-known that it is not sufficient to test each guard in turn and allow other events to occur concurrently. This would lead to a situation in which a guard becomes false again (due to some external influence), while the rest of the guards are being evaluated. This would defeat reasoning about the validity of guards on entry to the method, a core requirement for contract checking and auditing. While in Meyer’s proposal, actual parameters passed as socalled separate (or remote) references are all considered exclusively accessed resources, our method gives the designer more control over

this. Locally referenced objects can be reserved for exclusive access and the heavyweight mutual exclusion resource control is not automatically applied to all remote references when these are meant to be passed only to some remote call inside the method body. In the Java translation, we can partly map holds to synchronised, but use wait-and-notify loops to implement the full semantics of the required synchronisation. 3.2. Atomicity with event counters As shown in Figure 4, at the time a request is made to execute a method, the req# event counter is incremented by one and that method goes into a pending state. This counter may be used within the method specification to control synchronisations and to model object states. The transition called Schedule Method & Hold Objects then gets performed when all guards are true and all required objects available. At this point in time, the begin# counter is incremented by one and the body of a method starts executing. After method execution, the finishing tasks are performed and the end# counter incremented in another atomic event. Incrementing the end# counter represents the completion of method without exception. The three counters associated to method execution may be used in the specification of awaits guards or be used to model state variables of the class within the holds construct. An example is shown in the previous section. Formally, the value of the integer floor is defined as end#continueUp - end#continueDown + 1. This is interpreted as the value of floor being the total number of times the continueUp method has completed, minus the total number of times the continueDown method has completed, plus one. Every time the continueUp method is executed successfully, the lift is modelled as having travelled up one floor. Conversely, for continueDown, the lift is modelled as having travelled down one floor. Initially, all lifts are at the bottom floor 1 (i.e., 0 - 0 + 1). The updating of variables declared in the holds constructs must also be done as an atomic unit. The value assigned to floor depends on the continueUp and continueDown methods. If a continueUp method call were allowed to complete while the number of continueDown executions is being counted, the true value of floor may be incorrect. Consequently, the values of up and down must be re-evaluated when the value of floor changes. To resolve the inconsistency, an acyclic graph of dependencies is created to model all variables that depend on method event counters. When one of these counters changes value, an atomic event is triggered that will update all dependent variables in the hierarchy before an executing method proceeds towards its next state. Specifically, this is embedded within the call, Schedule Method & Hold Objects and Method Returns & Release Objects transitions. After the method body has executed, the Method Returns & Release Objects transition releases all objects that were acquired on entry and increments the end# counter and hence triggers the updating of all dependent variables defined in the acyclic dependency graph. Optionally, assertion checking is performed as part of the progress of method executions. Unlike traditional contract design, in distributed systems we distinguish between client-side and server-side checks. 4. Related work

A discussion on component software must include a survey of the current practices of component technology. The three standards are OMG’s CORBA, Microsoft’s COM/DCOM and Sun’s JavaBeans. Like these standards, our work supports the design notion of separating and hiding the

component’s implementation from its interface, which controls messages entering or leaving the component. We further use Eiffel-style assertions to describe synchronisation of components and to better understand multiple threads of execution in distributed systems. Java has been annotated with Eiffel-style assertions in the Java design-by-contract tool called iContract [4], which is run as a pre-processor over a set of annotated sourcecode files. iContract provides explicit software contracts for Java in a sequential object-oriented environment, while the annotation in our work also addresses the object synchronisation issues for concurrent systems in a component-oriented environment. Myer’s proposal [8] for concurrent object-oriented systems relies solely on sequential object semantics. Our approach distinguishes synchronisation constraints from the usual assertions, such as pre- and post-conditions, attached to objects. Unlike Meyer’s proposal, components can be multithreaded and the inheritance of methods does not change the semantics of constructs (such as calls or pre-conditions), depending on whether they are inherited into sequential or concurrent objects. All synchronisation constraints in our approach become part of the interface contract protocol which may be implemented by more or less sequentialised code. The code itself is free from low-level and imperative synchronisation constructs. To accommodate the requirements of sequential objects, Schmidt [16, 14, 15] has extended the principles by providing a type system, calculus and an operational model for these components. One result is a proposal for a local conformance constraint, which is weaker than those defined for Eiffel. Unlike Eiffel, it caters for both contravariant typing and contravariant conformance, and guarantee substitutability [12]. In our current work, a similar semantic model is employed to formulate rules for components in a distributed environment. The local conformance constraints have been extended to include concurrency notions. With these extensions, it has been pointed out that the substitutability and global correctness mentioned earlier also hold for concurrent systems, under the assumption that pre-conditions, once established by the caller, are established if and when, eventually, the callee proceeds [13]. Darwin [5] is an architectural description language that promotes component-based program structuring in which the component hides its behaviour behind a well-defined interface. Our design tool is an extension of the visual programming tool, Software Architect’s Assistant (SAA) [9] from the Darwin project, in which programs are built by declaring component instances and providing the required interface bindings. While Darwin is object-based, our abstractions extend subtyping, multiple and partial inheritance to object-oriented architectures and interfaces in the presence of synchronisation interface elements. 5. Conclusion

We have demonstrated a component model and design approach for distributed systems. It is characterised by separating aspects of system architecture from interface definition and computation, and by stressing contractual specifications to achieve a high level of trust in the reliability and robustness of such systems in the presence of flexible and dynamic extension, replacement and reconfiguration of entire subsystems. To this end, Eiffel-style assertions and design-by-contract were extended to handle aspects of distribution and concurrency and were associated to architectural and interface elements. These were demonstrated in a Java binding and supported by an architectural definition tool. The lift control system served as a case study. Static analysis and consistency checks need to be performed on components against published interfaces or contracts. We described a tool architecture which allows us to integrate existing analyser tools and provers to formally analyse aspects of compatibility

and interoperability of components in distributed systems. It is quite difficult to prove that components appropriately meet their contracts – especially in reconfigurable and dynamically assembled and changing systems. Therefore, the synthesis of monitoring code from contracts is even more important in distributed systems than in sequential software. To gain a high confidence that our implemented components are compatible, we also need to relate test scenarios to contracts. Within a system of interoperable components, our aim is to select test cases that will exercise the interactions between components in a systematic manner. Through a careful selection of test cases, deviation from a component’s intended interface or contract will become detectable and sometimes correctable. References

[1] J. R. Abrial. The B-Book. Cambridge University Press, 1996. [2] M.Y. Ben-Gershon and S.J. Goldsack. Using inheritance to build extendable synchronisation policies for concurrent and distributed systems. In Proceedings TOOLS Pacific 95. Prentice-Hall, 1995. [3] S. Ferenczi. Guarded methods vs. inheritance anomaly - inheritance anomaly solved by nested guarded method calls. ACM SIGPLAN Notices, 30(2):49–58, February 1995. [4] R. Kramer. iContract - the design by contract for java tool. In Proc Technology of Object-Oriented Languages and Systems (TOOLS 26), Santa Barbara. IEEE Computer Society, 1998. [5] J. Magee, N. Dulay, S. Eisenbach, and J. Kramer. Specifying distributed software architectures. In Proceedings 5th European Software Engineering Conference, ESEC 95, September 1995. [6] S. Matsuoka and A. Yonezawa. Analysis of inheritance anomaly in object-oriented concurrent programming languages. In G. Agha, P. Wegner, and A. Yonezawa, editors, Research Directions in Concurrent Object-Oriented, pages 107–50. MIT Press, 1993. [7] B. Meyer. Eiffel - The Language. Prentice Hall, 1992. [8] B. Meyer. Systematic concurrent object-oriented programming. Communications of the ACM, 36(9):56–80, Sept 1993. [9] K. Ng and J. Kramer. Automated support for distributed software design. In Proceedings 7th International Workshop on Computer-Aided Software Engineering (CASE ’95), Toronto, July 1995. [10] J. L. Peterson. Petri Net Theory and the Modeling of Systems. Prentice-Hall, 1981. [11] A.S.M. Sajeev and H. W. Schmidt. Integrating concurrency and object-orientation using boolean, access and path guards. In Proc. 3rd Int’l Conference on High Performance Computing, pages 68–72. IEEE, 1996. [12] H. W. Schmidt. Information Systems Interoperability, chapter 6: Compatibility of Interoperable Objects. Research Studies Press Ltd, 1998. [13] H. W. Schmidt and J. Chen. Reasoning about concurrent objects. In IEEE Proceedings Asia-Pacific Software Engineering Conference (APSEC ’95), Brisbane, Australia, pages 86–95, 1995. [14] H. W. Schmidt and W. Zimmermann. A complexity calculus for object-oriented programs. Object-Oriented Systems, 1(2):117–47, 1994. [15] H. W. Schmidt and W. Zimmermann. Reasoning about complexity in object-oriented programs. In Proceedings of the IFIP Working Conference on Programming Concepts, Methods and Calculi (PROCOMET 94), San Miniato, Italy, pages 541–60, 1994. [16] Heinz W. Schmidt and J. Chen. Reasoning about concurrent objects, tr-95-05. Technical report, School of Computer Science and Software Engineering, Monash University, 1995. [17] C. Szyperski. Component Software: Beyond Object-Oriented Programming. Addison-Wesley, 1998.

Suggest Documents