Back to index

2 Semantics of Message Passing

In this chapter, we investigate the area of flexible message passing semantics. We first give a small overview and clarify some basic concepts. Section 2.2 defines some basic terminology and shows its relation to our composable message semantics. Finally, in Section 2.3, we relate our work to previous work in the area of flexible and customisable message passing semantics.

2.1 Overview

The dynamic semantic of object-oriented languages are based on the message passing mechanism. A message is a request for an object to carry out one of its operations. Since objects can only communicate by sending messages, message passing is the basic means for creating execution paths in the system.
To cope with the complexity of today's distributed systems, we think that higher-level mechanisms are needed to effectively structure, abstract and reuse not only objects themselves, but also interactions between objects.
It is especially appealing to combine/mix object-oriented programming and distributed systems. One can describe them both as "little computers that communicate with each other". However, this metaphor is dangerous. Guerraoui and Fayad [GuFa99] point out that distribution transparency is impossible to achieve in practice. Precisely because of that impossibility, it is dangerous to provide the illusion of complete transparency. One cannot just take a set of locally interacting objects and distribute them over a set of hosts. E.g. local objects cannot fail independently and the communication between them always works. However, hosts in a distributed system may fail independently and connections between two objects may fail temporarily or even permanently. The increased latency of a remote method invocation might become another concern. Network preparation (marshalling and unmarshalling) and transport increase the latency of all invocations considerably. One cannot just hide all of these details and expect a robust and efficient implementation.
This leads us to the conclusion that it is not possible to hide distribution, even, that distribution should not be hidden. Our mechanism of composable message semantics tries to solve this problem, while it still retains some of the comfort introduced by hiding the network. We try to let the programmer be aware of the network, but not necessarily have to care about it and offer him/her the possibility to adapt the program to the network and his/her requirements and to adapt the applied distribution technique to his/her application. We separate the aspects of the basic functionality from the aspects introduced through distribution or multi-user support, e.g. transport, synchronisation, transactions and replication.
We use the programming language Oberon in this thesis as the base for our prototype implementation. It plays the role of a research vehicle for demonstrating and verifying the concepts that are introduced in our message semantics framework. However, the concepts of our work are independent of any given programming language as long as it offers support for meta programming, i.e. if it allows a programmer to obtain information about classes and methods at run-time.
Similar to the network objects of [BiNOW95], we restricted our features to those we believed to be vital for distributed objects systems (distributed type-safety, transparent invocations, powerful marshalling, efficiency and distributed garbage collection) and added our composable message semantics framework.

2.2 Terminology

In the following sub-sections, we examine some terminology that is often used in the remainder of this thesis. Most of the terminology has been introduced by [Smith82] and [Maes87]. We also point out where our composable message semantics come into the picture.
Message Passing
The dynamic semantics of object-oriented languages are based on the message passing mechanism. A message is a request to an object to carry out one of its operations. The most widely used mechanism to pass a message is the invocation of operations on the target object. The terminology differs between different languages. Oberon [MöWi91] uses type-bound procedures, C++ [Stro86] has virtual methods and Java [GoJS96] offers methods.
We use the term message passing in all of these cases. Messages are passed by using a call-like action. Message passing using explicit event objects is excluded.
Reification
Reification is the process of making a concept explicit and thus available as a first class object. An example of reification is the conversion of a method invocation (receiver, several arguments, method selector) into a message object. The reified message can be examined and handled using meta-programming facilities.
Meta Programming and Reflection
There are different definitions of reflection and different ways as to how reflective information is presented to a program. Maes [Maes87] defines a reflective system as a system which incorporates structures representing (aspects of) itself. This self-presentation makes it possible for the system to answer questions about itself and support actions on itself.
Sometimes, reflective computation directly contributes to solving a problem, e.g. introspection in a debugger. However, more often it supports and eases the internal organisation of a system. We use reflection in the second sense, by changing the semantics of method invocations in order to separate the program logic from other aspects, e.g. distribution. McAffer [McA95] states that reflection allows us to separate what an object does (its base level) from how it does it (its meta-level).
One possibility to structure reflective information are meta-classes [GoRo83]. The message passing behaviour of an object is specified in its class and the behaviour of a class in its meta-class. Finer-grained possibilities are meta-objects where every object has its own meta-object. This object contains all of the information that is available about the object. This results in a clear distinction between object-level and meta-level.
Composable message semantics use meta-objects to describe the semantic behaviour of invocations of their methods. Each object may have its own meta-object (meta-objects are shared if the semantics of two or several objects are equal). We even extend this design by allowing several meta-objects for one and the same object. Using this extension, we gain the ability to present an object through multiple distinct views. Each of these views defines its own unique semantic on how the methods of the base object are invoked.
Reflective systems separate design concerns. In general, each object in a reflective system is bound to a group of meta-entities describing the object's behaviour [KicRB91]. Each such meta-entity is also bound to another group of meta-entities and so on. Although very flexible, reflective systems have complicated semantics and they may bring unnecessary additional complexity. We believe that since the only way to access an object is to invoke one of its operations, it suffices to control the invocation process in order to express various different behaviours. In fact, our semantic building blocks can be viewed as dedicated meta-objects and our composable message semantics framework can be seen as a pragmatic reflective tool. Ferber [Ferb89] distinguishes between three reflection models:
  • The meta-class model, which is based on an equivalence between the meta-object and the class of the object does not allow for different meta information (semantics) for objects of the same class.
  • The meta-object model, where meta-objects are instances of a class META-OBJECT or of one of its subclasses. The class of an object corresponds to its structural aspects while the meta-object corresponds to its computational aspects. This allows for modifications of the meta-object, i.e. it allows for different meta information for different objects of the same class. It also allows new message handling techniques by creating additional subclasses of META-OBJECT.
  • The meta-communication model, which is based on the reification of the message. Invocations are intercepted and a special message object is created. This object contains the arguments of the invocation, the receiver and the selector that specifies the invoked method. By subclassing the message class it is possible to define new types of message passing semantics.
Our composable message semantics do not fit perfectly into this classification. The closest match is the meta-communication model. Our composable message semantics define the applied communication scheme. However, we go a step further and allow several views of one and the same object.
Distribution and Concurrency in an Object-Oriented Environment
There are many projects that combine the object paradigm with concurrent and/or distributed programming. The data abstraction and the message passing metaphor seem suitable to ease the development of the corresponding applications. Briot et al. [BrGL98] classify these projects into three categories:
  • The library approach structures concurrent and distributed systems with the help of class libraries. It offers classes for various components of the system, e.g. processes, files and protocols ([GaGu97], [HaPM93], [Wakita93], [ThTK98]).
  • The integrative approach unifies the concepts of object-orientation with the concepts of concurrent and distributed programming ([Bos89], [GCLR92b], [OMGc], [AgKP94]). For example, merging object and process to an active object.
  • The reflective approach tries to separate the application program from the various aspects of concurrency and distribution ([GaGu97], [RoKC99], [SiSC97], [Yokote92], [Ledou99], [GoRo83], [AWBB94]). These aspects are described in meta-programs. This enables run-time customisation and adaptation.
We chose a mixture between the first and the third approach. We use reflection to specify the desired message semantics and libraries to specify new kinds of semantics. Every invoked method may have its own semantic especially tailored for it. The invocation of this semantic happens completely transparently. A semantic is assigned to only one or several methods using reflection. The semantic itself is a composition of an arbitrary number of basic building blocks (one invocation abstraction and several filters). The building blocks constitute a library from which a programmer may either choose existing blocks or build new ones that match his/her specification.
Every approach has advantages as well as disadvantages. The library approach has no restrictions, i.e. one can add new classes for all new concepts. However, the programmer has to explicitly deal with the library. The achieved degree of transparency is relatively low.
The integrative approach adds high-level constructs to the language. This reduces the implementation effort necessary to write distributed/concurrent applications, as the language itself handles many of the problems introduced by concurrency or distribution. However, the set of high-level constructs is fixed and cannot be extended.
The reflective approach combines the advantages of the other approaches. It offers the increased transparency of the integrative approach together with the extensibility of the library approach. An application developer can use high-level constructs that are supplied by other programmers, which extend and customise the system. However, the increased flexibility of the reflective approach has some drawbacks as well. First, it increases the overall complexity of the system. Briot et al. [BrGL98] state that they believe that, independent of the required cultural change, this is the price to be paid for the increased, albeit disciplined, flexibility. Second, reflective systems offer reduced efficiency due to the extra indirections and interpretation. We claim that this thesis presents a possible way to eliminate the second drawback and will show that the reflective approach does not need to be slower than non-reflective approaches.

2.3 Related Work

The main contribution of this thesis is the framework for composable message semantics. In this section, we compare our work with similar techniques introduced in other projects. Thus, we do not compare with programming languages that adopt the conventional object model, such as Smalltalk [GoRo83], Eiffel [Meyer92] and C++ [Stro86], but discuss some languages and systems that provide specific constructs for defining the semantics of object interfaces and the manipulation of sent/received messages.
We split this section into several parts. We first discuss some projects that offer message passing functionality with a fixed set of semantics. Second, we present some projects that, similar to our composable message semantics, introduce an open framework that allows to create and apply an arbitrary number of new message semantics. In the remaining sub-sections, we present several interesting projects in depth. We compared all of these projects with our composable message semantics. Some of the mentioned projects implement more aspects than we can cover in this comparison. Therefore, we restrict the comparison to the aspect of message passing.
Projects with a Fixed Number of Offered Semantics
There are several proposals that try to add one or a fixed number of new message semantics to an existing system:
  • The Legion object model [LeGr96] offers point-to-point and replicated invocations.
  • Baquer [Baqu96] introduces the notion of indirect calls, where a message is intercepted and written to a persistent storage. Later, the message is read, transmitted over the network and executed on another host. They use an interface specification language (ISL) to declare their interfaces. The stub & skeleton code snippets are generated automatically.
  • Joshi and Janaki Ram [JoJa99] offer a fixed set of different invocation semantics. They use this set to address some issues specific to parallel programming on workstations. Their anonymous remote computing (ARC) tries to give the system better facilities to balance the load within a cluster.
  • Pandey and Hashii [PaHa99] introduce another domain specific extension of the available message semantics. They introduce an access control mechanism for invocations in the context of mobile Java code. Programmers specify constraints in a special access constraint specification language. Whenever a new class is loaded into the run-time system, a bytecode editor is activated that patches the incoming class file by adding byte codes that ensure the previously specified constraints. Even though the actual changes happen at run-time, it is actually a static mechanism, as the constraints must be defined in advance and are compiled with a special access constraint compiler. Another restriction of this scheme for new invocation semantics, is its fixation on decorations. In our terminology, it supports only invocation filters but no invocation abstractions.
  • Another domain specific extension is presented in [FroA93]. They introduce multi-object constraints that allow programmers to express multi-object co-ordination. The formal constraint description is translated into synchronizers. A synchroniser is a special object that observes and limits the invocations accepted by a set of ordinary objects. A similar approach is used by Actor languages [AgKP94]. They introduce special language features for inter-actor synchronisation and actor placement.
  • DCOM [EddE98] and COM+ [Eddon99] are other examples for a model with a fixed set of semantics. Besides the default RPC, they also include support for transactions. However, the interception model of COM+ is not open for new services.
  • Thiruvathukal et al. [ThTK98] introduce reflective remote method invocation into Java. They try to remedy some of the disadvantages of RMI (fixed semantic, fixed base class, fixed transport) [RMI] by using the reflective capabilities of the Java environment [JavaR]. The programmer has to define the actual parameters by using reflective features in order to build an invocation descriptor. By calling either invoke or invokeAsynchronous, one executes the invocation descriptor. This scheme is extremely flexible, but also extremely complex to handle.
  • Similar flexibility is introduced in Harmony [Wakita93]. The programmer can define new Communicators based on predefined primitives. As in [ThTK98] one completely loses invocation transparency, as the chosen semantic has to be specified explicitly with every invocation. We see another disadvantage of both, [Wakita93] and [ThTK98], in the viewpoint of the semantics. In their approach, the sender decides on the semantic. Our composable message semantics delegates the decision about the applied semantics to the receiver. It is the receiver's duty to decide which semantics are acceptable or preferable. If this decision is ambiguous, it may offer two or even more views of itself.
All of the above approaches do not succeed in substantially increasing the programmers flexibility. A single added semantic can close the gap between the offered and the desired semantics only for a short period of time.
Projects with an Arbitrary Number of Semantics
We consider approaches that allow an arbitrary number of semantics as superior to approaches that add only a fixed number of new semantics. Our composable message semantics allow arbitrary new semantics. Therefore, we elaborate on most extendable systems individually in the following sub-sections. This sub-section describes three only marginally related projects that use the extendable approach:
  • An approach, similar to our composable message semantics, is the channel reification proposed by Ancona et al. [ACDG97]. Every invoked method is reified in a message and handled according to the assigned meta-computation. The meta behaviour is defined on a per method basis, i.e. it does not support multiple views. The generated message is separate from the receiver, i.e. it cannot access information stored in the receiver. Reified channels do not really separate the message from the channel itself. They have not implemented a prototype and the description is a little bit vague. [ACDG97] propose no technique to implement channel reification efficiently. Concrete channel types for different applications are stored in a library. We emphasise the concept of the framework and explicitly use composition techniques in order to increase reusability. Reified channels restrict the flexibility to the transmission process of the message. The message's generation, i.e. the marshalling process is not adaptable by the channel's type.
  • Another approach using reflection is the architecture for next generation middleware proposed by Blair et al. [BCRP98]. They introduce per object meta-spaces and define a language independent framework to introspect and adapt the behavioural aspects of one or several objects. The actual achievable flexibility depends on the chosen language and the system actually used. We see this approach as a layer on top, e.g. our composable message semantics. The architecture proposed by [BCRP98] would be able to offer different methodologies ([ACDG97], [GuGM97] and composable message semantics) in a single environment.
  • The FlexiNet platform [HayHD98], a Java middleware, proposes to reify the layers of the communication stack into different meta-objects. Each meta-object represents a specific aspect such as call policy, serialisation, network session, etc. The client-side stub code is fixed, i.e. it uses a fixed model for parameter passing. It passes references to interfaces by reference and passes objects by copying. Another disadvantage is the missing skeleton code on the server-side. The top layer on the server invokes the actual method using the Java Core Reflection [JavaR]. This introduces a significant overhead. We currently only reify characteristics dealing with the upper layers of the invocation mechanism. We handle the protocol part within the assigned client-side invocation abstraction.

Encapsulators
The encapsulators framework [Pascoe86] offers an approach that is similar to our composable message semantics. An application object can be surrounded with a layer that intercepts messages that are sent to the object and the replies of those messages. Encapsulators are special objects that implement this layer. An encapsulator defines a pre-action that is executed upon message reception and a post-action that is executed when the result of the message is returned.
Encapsulators are implemented as an extension to the Smalltalk-80 system. Applications of the encapsulators mechanism that are discussed in [Pascoe86] are a Monitor, which enforces mutual exclusion for Smalltalk objects and a Model, that generates triggers when certain messages are executed by the object.
x <- Monitor object: anObject
The actions in encapsulators are straightforward Smalltalk method implementations. Contrary to the composable message semantics, there is no distinction between caller-side and callee-side, multiple views are not supported and encapsulators cannot be composed, i.e. an encapsulator roughly corresponds to an invocation filter in our terminology. Encapsulators impose a significant overhead as, during every invocation, the run-time system has to search the correct encapsulator within the encapsulator hierarchy (incoming messages are trapped by the doesNotUnderstand method that is redefined in the class Encapsulator). Additionally, encapsulators require changes to the Smalltalk type system.
Procol
This concurrent object-based language ([BoLa89], [Laffra92]) provides a mechanism called protocol that defines the interaction protocol between the sender and the receiver of a message. It emphasises fine-grained parallel execution with one-way synchronous messages. Message are sent 1-to-1, 1-to-n or n-to-1 and can be restricted with a protocol specified in the receiving object. A protocol offers the tools to order and constrain the possible messages. It determines the one Ð or more Ð messages that are expected next according to specified regular expressions. Secondly, the protocols can be augmented with guards, which are boolean expressions on the internal state of the object, e.g. values of instance variables. Protocols serve the following purposes [Laffra92]:
  • Interface specification for other objects.
  • Interaction sequencer between objects.
  • Access controller to the methods of the object.
  • Type or identity checker on clients.
Protocols offer some support to add attributes to invocations. However, they are not the base of a generic framework for new invocation semantics. This is also the case with the 1-to-1, 1-to-n and n-to-1 invocations. Messages are always sent/received by either an object or a type, i.e. all objects of this type. Another severe drawback is the restriction to one-way messages. [BoLa89] state that they will introduce a send-with-reply primitive. However, we found no papers on this subject and it would still not be a general solution.
The ACS Protocol
The Apply-Call-Send (ACS) protocol focuses on atomic actions in distributed systems and builds on the KAROS [GCLR92a] language. ACS implicitly merges the nested actions model and nested asynchronous request messages. They extend the restrictive notion of synchronous RPC [BiNe84] by using several models of asynchronous request messages. This allows further exploitation of the parallelism intrinsic to the given application. Every request message implicitly generates an atomic action. Depending on the chosen model, a nested or a top-level action is generated.
res = Apply(server, class, method) << Arg1 << ... ;
This sends an Apply request to the specified target server. An Apply automatically starts nested sub-actions on the client and on the server. The result of the message will be stored in the implicit future res (see [Hals85] for more information on futures). The two sub-actions are safely executed in parallel. If one of them fails, the system aborts the parent action, i.e. the action that executed the Apply. The second model for sending requests is Call. A Call request is handled similarly to the Apply request. If both sub-actions succeed, it has the same semantic. However, if a failure occurs, a Call request does not abort the parent action, i.e. the sub-actions can fail independently of each other. Finally, the Send request creates a new independent top-level action that executes on the server. This allows one to safely break atomicity to trigger an independent activity.
ACS tries to enhance the communication infrastructure by supplying different communication techniques. A programmer has to explicitly choose whenever she/he wants to issue a remote invocation. This approach is inverse to our composable message semantics. In ACS the caller decides on the mode of the execution. With composable message semantics, it is the duty of the receiver to determine the semantic used by its caller, i.e. we couple the semantics together with the receiver.
A further difference is the genericity of the approach. ACS tries to offer an enhanced communication model for asynchronous distributed communications in order to ease the development of reliable distributed programs. To achieve this, it defines three communication models and adapts the language accordingly. Our approach of composable message semantics is more general. We do not target such a specific domain.
Apertos
Apertos is an object-oriented reflective operating system designed for open and mobile computing environments [Yokote92]. Apertos introduces object/meta-object separation within the operating system design. An object is associated with a group of meta-objects, which define the semantics of the object. An object can change its meta-objects by migration. Although Apertos provides a general reflection framework, it does not emphasise abstraction and reuse of interactions among objects. It offers three different primitives for message passing. Send invokes a method asynchronously. Call and Force invoke synchronously, but use different priorities. It is not possible to add further message semantics.
Spring and Subcontracts
The distributed operating system Spring introduces the concept of subcontracts [HaPM93]. Invocations are intercepted by the generic stub code, which uses a subcontract to specialise the method invocation according to the specification (see Figure 2.1).
Every object has an associated table of subcontracts. One may change one, several or even all subcontracts, but it is not possible to create different views of one and the same object (contrary to our composable message semantics). However, it is possible to have objects of the same type with different subcontracts. Another restriction is that subcontracts are not aware of the invoked selector, i.e. they cannot act depending on the invoked method.
[HaPM93] note that applying subcontracts has some disadvantages compared to of specialised stubs. First, the stub code invokes the subcontract dynamically in order to marshal the arguments and to invoke the method. This dynamic behaviour decreases the overall performance. More significantly, subcontracts make it harder for the stubs to be optimised for particular sets of arguments and results, as the subcontract may be adding an indeterminate amount of data to the call and/or reply buffer. Most of these problems could be solved using a technique similar to our just-in-time stub generation, as such stubs could be tailored to the actual run-time environment. This would avoid the need for specialised combinations of stubs and subcontracts as proposed in [HaPM93].
GARF and BAST
GARF [GuGM97] is a tool for programming reliable distributed applications on top of the Isis toolkit [BirRen94]. It offers design and programming support on a higher level. It separates the functional (what an object does) and the behavioural (how does it communicate) features of the program. Synchronous point-to-point RPC is the central model. However, the programmer may change the behaviour of the communication by using behavioural objects. An encapsulator wraps around an object and defines how messages are treated. A mailer actually performs the communication. Both, encapsulator and mailer, are exchangeable.
The basic ideas of GARF are quite similar to the ideas underlying our composable message semantics. Encapsulators are similar to our filters and a GARF mailer is similar to an invocation abstraction in our framework (mailers are more restricted by the usage of the Isis toolkit). However, there are some significant differences.
The first obvious difference is the granularity, in which the programmer can decide on the communication behaviour. GARF is class-centric, i.e. all objects of a class use the same encapsulator. Our approach is instance-centric.
Another difference is the restriction of GARF to only one encapsulator per object. All incoming and outgoing messages are passed through this behaviour object. This leads to a different behaviour than is expressed by our model. A sent message is passed to the encapsulator of the sender, i.e. the sender decides on the chosen semantic. Our composable semantics are mainly defined by the callee, i.e. the receiver.
Another difference is the aspect of composition. Our message semantics are inherently composable. It is our opinion that it is necessary not only to have a library of different semantic actions, but to see these actions as building blocks for more complex semantics. GARF does not deal with this aspect and offers only a limited set of semantic actions. To add new kinds of semantic behaviour one has to implement new types of encapsulators or mailers. For this task, one must be familiar with the GARF system and even with the underlying group communication system.
GARF puts more emphasis on the transport layer, i.e. the mailer. A mailer builds on the group communication toolkit to achieve more reliable systems by replication. We omitted this aspect, as we've put more emphasis on the generality of our approach and see the introduction of a more flexible transport layer as a straight forward extension of our framework.
BAST [GaGu97] is an extension of GARF that replaces the fixed transport layer with a framework for protocol composition. Protocols are composed using the Strategy [GHJV95] pattern. However, BAST focuses on the interaction of protocols on lower levels than described in this thesis.
Sina and Composition-Filters
The composition-filters object model extends the basic object-oriented model [Berg94]. It emphasises the support of large-scale software, i.e. it tries to ease the management of such systems. The model is class-centric, i.e. the objects of a given class all have the same access semantic.
Objects communicate by sending messages. A message is delivered following a request-reply model. The caller sends a message and halts its execution until it receives a reply. The callee is responsible to either execute the request or eventually reject it. The composition-filters model is implemented in the programming language Sina [Sina].
Each object consists of a kernel and an interface. Besides state information and methods, it may have, conditions and filters. A condition is an attribute that represents a characteristic of the object. It is computed - on demand - out of the object's state information.
The filters are the central aspect of the composition-filters paradigm (see Figure 2.2). They are defined as an ordered set that is valid for all methods of the class. Input filters are checked whenever a message arrives at an object. They define if and how the message should be handled. Output filters are checked before a message is sent to its destination. This distinction is similar to our caller-side and callee-side semantics. A message passes these filters, until it is either discarded or until it is dispatched. For input filters, dispatching means the execution of the method, for output filters, it means that the message is sent to the target object. A filter consists of three parts. A condition, which specifies the prerequisites in order to continue the evaluation of the filter, a matching part that compares the incoming message with a pattern (receiver and method) and a substitution part, which specifies replacement values for the receiver and the method of the message (the substitution part may be omitted).
inputfilters
  select: Error = { True=>[*.getFullName]*.*, IsBirthday=>[*.getPresent]*.* };
  disp: Dispatch = { inner.* };
outputfilters
  continue: Send = { [x.*]y.*, * };
The above examples demonstrate three different filters. There are several different filter-types, however, in this comparison we restrict ourselves to the four filter-types relevant for the comparison (Error, Dispatch, Send, Meta). Every filter starts with a name (label) and a filter-type. Our first example uses the filter type Error. An Error filter lets matching messages pass to the next filter. If a message does not match, an exception is raised and the execution is halted. We define two filter elements between the curly braces. True=>[*.getFullName]*.* is always active (True) and accepts invocations of getFullName on all objects (*.getFullName). It substitutes these invocations with *.*, i.e. it just lets them pass as they are. The second filter element is only active if the condition IsBirthday evaluates to true. In this case our filter also accepts invocations of the method getPresent.
Our second example uses the filter-type Dispatch. It uses a shortened syntax expressing that this filter accepts all legal messages and forwards them to the actual implementation of this class (inner), i.e. the messages are dispatched and executed. Without this filter all incoming messages would be rejected.
The third example is an output filter of type Send. A message accepted by such a filter is sent to its destination, i.e. its target object. There are two filter elements. The first accepts arbitrary invocations on x and substitutes the receiver x by y, i.e. all messages sent to the object x are actually sent to the object y. The second filter element accepts all requests as they are, i.e. the receiver is not substituted.
Using the above filter types, it is possible to change Ð within limits Ð the behaviour of method invocations, e.g. one can generate multiple different views of one and the same object (only with severe restrictions).
exclude: Error = { SentByManager=>putTask, {SentByManager, SentBySecretary}=>getWorkingHours };
This filter accepts invocations of getWorkingHours by all clients. However, in order to invoke putTask, the caller must be a manager (SentByManager is a condition that checks the run-time type of the caller).
In order to circumvent the compile-time fixation of the composition filters framework, [AWBB92] and [AWBB94] introduced the concept of "Abstract Communication Types" (ACT). An ACT is an ordinary Sina class with the same semantics and syntax. It is a class working on instances of the type Message and is associated with some objects with help of the Meta filter-type. A Meta filter converts accepted messages into instances of type Message (reification) and forwards them to the associated ACT. After the ACT finishes, the message is passed to the next filter.
inputfilters
  reifyIn: Meta = { [*.*]bigBrother.logMessage };
In the above example, the Meta filter reifies all incoming invocations and passes them to the method logMessage of the ACT bigBrother. The ACT sees a message as an instance of the class Message passed to its method logMessage. The type Message offers methods to access its stored information. The ACT allows one to manipulate the semantic of a message invocation. One can change the message contents, change the semantic to multi-cast and broadcast or change the synchronisation properties of sent messages.
There are four main differences between our composable message semantics and composition filters. First, we use an instance-centric approach, whereas composition filters are class-centric. The filters are a fixed part of the interface definition of a class, i.e. at run-time every instance has the same filters and the filters cannot be replaced dynamically. However, run-time state information of individual instances may be part of the filter process. This enables the filtering behaviour to adapt during the life cycle of an object. Our composable message semantics are even more flexible. Each instance may have its own set of semantics that need not be related to the semantics of other instances and the semantics can be replaced dynamically.
The second difference lies within the expressive freedom offered by a method. Composition filters are more restrictive in what they can express, e.g. in order to support transactions they need a special new feature in their filter elements.
transact: Error = { TRUE=> <acc.*, self.*> }
The angle brackets contain an action that is executed atomically. In this case, the message is redirected to the two objects acc and self.
The third difference is in the expressiveness for post-conditions and class invariants. They cannot be modelled with composition filters in a natural way. The composable message semantics presented in this thesis cope naturally with post-conditions, as the semantics are active before and after the actual execution. Composition filters Ð input and output filters Ð are only handled before the method is executed. In order to support class invariants with our framework, one has to add an appropriate filter to every method. This is definitely not as elegant as would be desirable.
The forth difference is the performance of the systems. As [Berg94] says, the issue of performance has not yet been touched in the implementation of composition filters, as it is not their first priority. Filter handling requires, for every method invocation, a linear iteration over all filters and filter elements. In particular, ACT's reifying process slows down the message passing even further. There are experiments with a Sina compiler that translates into C++. However, this technique requires statically fixed semantics. It offers the same advantages and disadvantages as aspect-oriented programming [KLMM97] (see also below the sub-section on Aspect-Oriented Programming). On the other hand, the composable message semantics were designed and implemented from scratch with performance in mind. We try to offer increased flexibility with minimal cost.
CORBA and its Messaging Service
The Common Object Request Broker Architecture (CORBA) [OMGa], proposed by the Object Management Group (OMG), is a middleware specification that defines the basic mechanisms for remote object invocation through an Object Request Broker (ORB), as well as a set of services for the object management, e.g. Event Service [OMGb], Transaction Service and Messaging Service [OMGc]. Before the introduction of the event service, CORBA only supported strictly synchronous point-to-point invocation semantic. Only by using the dynamic invocation interface (DII), was it possible to achieve other semantics. However, this implies an increased overhead and destroys distribution transparency. There is also the notion of oneway operations. But their definition is too vague to be useful [OMGc]. Due to this restriction, a number of ORBs expose selected aspects of their underlying system (e.g. filters in Orbix or interceptors in COOL). However, this approach can be criticised for being rather ad hoc and it destroys inter-ORB portability.
With the introduction of the event service, the first real notion of asynchronicity was introduced into the CORBA specification. The event service specifies an event channel. An arbitrary number of clients may send and receive messages asynchronously over this channel. However useful this abstraction may be for some applications, it is still far from fulfilling all requirements, e.g. fault tolerance. The event channel is managed Ð per definition Ð by only one object on a designated host. The whole event channel fails as soon as this host fails or is no longer reachable. The concept of the event channel is just too narrow to be of general use.
The next step was the introduction of the CORBA messaging service [OMGc]. This service introduces a whole new set of different invocation abstractions. The messaging service introduces asynchronous method invocation (AMI), time independent invocation (TII), quality of service functionality (QoS) and a routing scheme for persistent invocations used by the TII. AMI defines two ways as to how invocation results are asynchronously transmitted back to the caller: call-back and poller. A call-back is an object reference passed as part of the invocation. As soon as the reply is available, the call-back object is activated with the data of the reply. In the polling model, the invocation returns an object which can be queried at any time to obtain the status of the outstanding request.
The TII allows for deferred invocations as common in mobile systems, where there is often no connection between the client and the server. Finally, the QoS allows clients and servers to define the set of required and supported qualities of service with respect to requests. [OMGc] defines a set of features defined for QoS, e.g. server-side queue ordering, lifetime of requests. A quality is defined as an interface derived from CORBA::Policy and the messaging service defines a framework that allows setting and querying of QoS.
We consider the messaging service as a big step in the right direction. However, we regard the step as too small and not ambitious enough. The added semantics increase the flexibility offered to programmers, but we already see new restrictions, e.g. CORBA still supports no true multicast. Our composable message semantics offer a much wider application domain. Currently, we have not implemented these semantics, but the framework is extensible enough to incorporate all the features proposed by the messaging service.
dynamicTAO
dynamicTAO ([RoKC99], [SiSC97]) is a CORBA compliant reflective ORB. It uses reflection to customise its behaviour at run-time. It concentrates on real-time constraints. A method invocation is reified and delegated to an Invoker. An Invoker is a client-side meta-object that describes the invocation semantics of the object. The corresponding server-side meta-object is called Dispatcher. Both, Dispatcher and Invoker, are dynamically exchangeable.
Our composable message semantics are finer-grained, as they are on a per method base. Unfortunately, we could not determine how the Invoker forwards invocations. We assume that it is using the DII of CORBA and thereby adding a substantial overhead. The look-up for the correct Invoker adds to this overhead. dynamicTAO uses another perspective on the customisable behaviour. It concentrates on the decoration of objects, the stub has an Invoker and the skeleton has a Dispatcher. We concentrate more on the communication between the two objects. Also, our clients may only decorate the semantics that were previously defined by the server object.
Another difference is the lack of the framework aspect in dynamicTAO. We offer a framework that allows the programmer to dynamically compose new semantics.
OpenCorba
OpenCorba is a reflective open broker [Ledou99]. By using reflection, it offers dynamic adaptability of the remote invocation mechanism via a proxy.
The OpenCorba IDL compiler generates the proxy class for the application and associates it automatically with the meta-class that is in charge of calling the real object. OpenCorba uses the Smalltalk implementation NeoClasstalk for the extended meta-programming functionality. The meta-class redefines the method invocation semantics on the proxy and carries out the remote invocation by using the DII CORBA API [OMGa]. This scheme is dynamically adaptable to support different mechanisms as, e.g. migration or replication.
OpenCorba uses ideas similar to the ones used in our composable message semantics. However, there are some major differences. The actual wrapping is done at compile-time (IDL compiler). At this time, the IDL compiler does not know which methods need to be wrapped. Thus, the technique of wrappers must be applied to all methods which again implies a cost that cannot be overlooked. Further, once the wrapper has forwarded the invocation to the meta-class (this needs another method invocation), the meta-class uses the DII of CORBA to actually transmit and execute the invocation. This poses another significant cost in run-time performance. Our approach uses proxies generated at run-time, i.e. our just-in-time compiler has all the necessary knowledge to generate efficiently adapted proxies.
Another difference between OpenCorba and composable message semantics is the framework aspect of the latter. It emphasises the fact that one cannot offer operational semantics for all circumstances, but one can only offer building blocks that ease the building process for the semantics actually needed by the current application.
Finally, OpenCorba uses a class-centric approach, in contrast to the instance-centric approach of the composable message semantics. This prohibits OpenCorba from supporting multiple views of one and the same object.
Voyager
The Voyager Project [ObjSpace] is an Object Request Broker (ORB) written in Java. It introduces three kinds of invocation semantics: sync, oneway and future. oneway defines that the invocation returns nothing to the caller and future defines an object that Ð eventually Ð will receive the result of an asynchronous invocation. Each of these semantics can either be point-to-point or multicast. However, only sync offers invocation transparency.
int price = market.buy(105, "SUN");
One may invoke methods dynamically by calling Sync.invoke. This is similar to the DII of Corba.
Result res = Sync.invoke(market, "buy", new Object[] {new Integer(105), "SUN"});
int price = res.readInt();
oneway and future invocations must be handled explicitly using the same dynamic invocation interface. Parameters must be passed as an explicit object array. There is no possibility to use these semantics in a transparent way.
Result res = Oneway.invoke(market, "buy", new Object[] {new Integer(105), "SUN"});

Result res = Future.invoke(market, "buy", new Object[] {new Integer(105), "SUN"});
//.. perform other operations
int price = res.readInt();

We think that the approach chosen in the Voyager project is extremely limited and offers only marginal support for alternative invocation semantics. However, the multicast mechanism fits nicely into the scheme and it offers transparent replication for sync invocations.
Aspect-Oriented Programming
Aspect-oriented programming (AOP) is a new programming methodology that tries to separate different aspects of an implementation [KLMM97]. Typically, some aspects, such as visualisation or synchronisation, are distributed over large portions of an implementation, i.e. they cross-cut the system's basic functionality. This cross-cutting has two drawbacks. First, it obscures the implementation of the actual system. Second, it makes maintenance a hard job, as changes to such an aspect may result in many necessary adaptations distributed all over the source code.
AspectJ ([LoKi98], [AspectJ]) is a general purpose language that implements the ideas of AOP. It is an extension of the Java programming language [GoJS96]. In AspectJ, one writes the base functionality and the required aspects separated from each other. Aspects contain code as well as semantic information about their placement into the basic functionality. A special pre-processor, called Weaver, parses this information and weaves the aspects into the basic functionality. After this process, the standard Java compiler is started and the resulting byte code is executed. This technique allows an arbitrary number of different invocation semantics.
[KLMM97] state, that while AOP is a new idea, there are already existing systems that have AOP-like properties. Many of the projects discussed in this section match this criteria. We think that composable message semantics do so as well. In contrast to AOP, we use a dynamic approach to weaving. This allows us to be flexible at run-time, e.g. we can change the semantic of an object. The static compile-time weaving of AOP severely restricts run-time customisation and adaptation.
Another restriction of AOP is its class-centric view. Every object of a class executes the same code, i.e. the same output of the weaver, as there is no way how one object could be inter-woven with aspects different from other objects of the same class. Therefore, it is not possible to have different views on different instances of the same class. Of course, this also inhibits the existence of multiple views on one object.
We see another disadvantage of the compile-time combination in the difference between the code that the programmer wrote and the code that gets executed at run-time. The output of the weaver is considerably larger and probably not easy to understand. This distinction complicates the debugging process considerably. Composable message semantics and other reflective approaches (RoKC99], [Yokote92], [Berg94]) do not have this problem as the code written by the programmer corresponds directly to the code that is executed.
An interesting example for AOP is the language Framework for Distributed Programming D [LoKi97]. It uses a sub-set of Java for the base functionality, Cool for synchronisation issues and Ridl for remote access strategies. A weaver combines these languages together and produces Java source code. In this special application both aspect definitions (Cool and Ridl) only influence how messages are sent and received. This makes the weaving process relatively simple. However, the above mentioned restrictions of AOP still apply.
Short Summary
All capabilities that are introduced by our composable message semantics already exist in one or several of the above described projects. However, each of these projects fails in at least one area which we deem vital. Either they support only a fixed set of semantics, or the introduction of an arbitrary number of semantics results in a considarably worse run-time behaviour. Another restriction of many approaches is the fixation to one semantic view per object or, even worse, one semantic view per class.

Back to index