Go home now Header Background Image
Search
Submission Procedure
share: |
 
Follow us
 
 
 
 
Volume 10 / Issue 7 / Abstract

available in:   PDF (107 kB) PS (80 kB)
 
get:  
Similar Docs BibTeX   Write a comment
  
get:  
Links into Future
 
DOI:   10.3217/jucs-010-07-0843

Implementing Coordinated Error Recovery for Distributed Object-Oriented Systems with AspectJ

Fernando Castor Filho
(State University of Campinas, Brazil
fernando@ic.unicamp.br)

Cecília Mary F. Rubira
(State University of Campinas, Brazil
cmrubira@ic.unicamp.br)

Abstract: Exception handling is a very popular technique for incorporating fault tolerance into software systems. However, its use for structuring concurrent, distributed systems is hindered by the fact that the exception handling models of many mainstream object-oriented programming languages are sequential. In this paper we present an aspect-based framework for incorporating concurrent exception handling in Java programs. The framework has been implemented in AspectJ, a general purpose aspect-oriented extension to Java. Our main contribution is to show that AspectJ is useful for implementing the concerns related to concurrent exception handling and to provide a useful tool to developers of distributed, concurrent fault-tolerant applications.

Keywords: aspect-oriented programming, exception handling, coordinated atomic actions, separation of concerns, distributed programming

Categories: D.3.3, D.2, D.3

1 Introduction

Exception handling [8] is a well-known technique for incorporating fault tolerance [2] into software systems. An exception handling system (EHS) offers control structures that allow developers to define and raise exceptions, indicating the occurrence of an error, and exception handlers, responsible for putting the system back into a coherent state. Handling contexts are regions where the same exception types are treated in the same way. When an exception is raised, the underlying EHS interrupts the normal processing and transfers control to an appropriate exception handler. If no appropriate handler is available, the exception is signaled, or propagated, to an outer context, usually the caller of the operation where it was raised.

Various modern object-oriented programming languages include EHS's. Although some of these languages natively provide constructs for concurrent programming, in all of them exception handling is purely sequential. Some authors [3] argue that special features for involving many concurrent objects in exception handling are so difficult to develop and use that only sequential exception handling should be employed. In spite of this, concurrent (or coordinated) exception handling [4] is a powerful tool for structuring large, distributed, and concurrent software systems [18], [21] and means for mitigating its inherent complexity are required.

Page 843

Aspect-oriented programming (AOP) [14] has appeared recently as a means for modularizing systems that present crosscutting concerns. A crosscutting concern can affect several units of a software system and usually cannot be modularized by traditional object-oriented design techniques. It has been argued elsewhere [16] that exception detection and handling are crosscutting concerns that can be better modularized by the use of aspect-oriented techniques. However, works on the subject which employ AOP have focused solely on the sequential EHS's available in programming languages such as Java [5], C++ [11], and C# [10].

Although the model of Java for exception handling is representative of many object-oriented programming languages, it is not well-suited for exception handling in concurrent systems. Java does not prescribe adequate rules for propagation of exceptions signaled by a participant of a group of threads cooperating in order to achieve a common goal. For instance, in Java, if a participant is unable to handle an exception, its thread is simply killed. This may produce incorrect behavior, such as inconsistent results and deadlocks. Furthermore, it is not possible to associate handlers to elements that are meaningful to the concurrent execution of the group of objects. Romanovsky and Kienzle [17] argue that problems such as these are due to the fact that exception handling issues are being considered separately from those of system structuring. The authors suggest that exception handling should have a natural integration with constructs for concurrent execution.

In this paper, we describe an approach to implementing an aspect-based framework that complements the EHS of Java with coordinated exception handling. This framework, which we call ACE (Aspect-based Coordinated Exception handling), was implemented in AspectJ [13], a general purpose aspect-oriented extension to Java.

Our goal is twofold: First, we want to analyze the benefits and disadvantages of using aspects to build a framework for coordinated error recovery, instead of relying on an exclusively object-oriented implementation [23]. Second, we want to provide support for the construction of reliable object-oriented systems with requirements such as concurrency and distribution.

This paper is organized as follows. Section 2 gives a brief overview of AspectJ. Section 3 describes the approach we employ for coordinated exception handling. Some background knowledge on sequential exception handling is assumed. Section 4 presents the design and implementation of ACE. Section 5 rounds the paper and presents some ideas for future work.

2 AspectJ Overview

AspectJ [13] is a general purpose aspect-oriented extension to Java. It extends Java with constructs for picking specific points in the program flow, called join points, and executing pieces of code, called advice, when these points are reached. Join points are used to capture crosscutting concerns, that is, concerns that affect several different program units and can not be modularized by traditional object-oriented techniques. A typical example of crosscutting concern is logging. The implementation of this concern must be scattered across all the modules in a system, because some contextual information must be gathered in order for the recorded information to be useful. Other common examples of crosscutting concerns include profiling and authentication.

Page 844

AspectJ adds a few new constructs to Java, in order to support the selection of join points and the execution of advice in these points. A pointcut picks out certain join points and contextual information at those join points. Join points selectable by pointcuts vary in nature and granularity. Examples include method call, method execution, field access, and class instantiation. A pointcut may be formed by the combination of various different join points selected only under specific conditions.

Advice are pieces of code that are executed when a join point is reached. These may be executed before, after, or around the selected join point. In the latter case, execution of the advice may potentially alter the flow of control of the application, and replace the code that would be otherwise executed in the selected join point.

The language also allows programmers to modify the static structure of a program by means of inter-type declarations. Inter-type declarations can introduce new members in a class or interface, such as methods and fields, or modify the relationships between types.

Aspects are units of modularity for crosscutting concerns. They are similar to classes, but may also include pointcuts, advice, and inter-type declarations. The following code snippet presents a simple aspect.

    01: public aspect SimpleAspect {
    02: public void Participant.exampleMethod() { ... }
    03: pointcut methodCallsFromParticipants(Participant p1):
    04: call(* Participant.exampleMethod(..)) && this(p1);
    05: before(Participant p1): methodCallsFromParticipants(p1){
    06: System.out.println("method called");}
    07: }

In the aspect above, line 2 presents an inter-type declaration that adds the method exampleMethod() to the type Participant. If the latter is an interface, the new method is added to both interface and implementing classes. Lines 3 and 4 present a pointcut that selects calls to exampleMethod(). This pointcut has one argument of type Participant, corresponding to the caller of the method (this(p1)). It selects calls to the exampleMethod() method, defined by the type Participant (Participant.exampleMethod(..)). The method may have any return type (as indicated by the "*" symbol) and take any parameters. Line 5 defines an advice that is executed before the join points selected by the pointcut in line 3. This advice simply prints the message "method called" on the screen (line 6). Aspects are associated to pure Java code by means of a process called weaving. Therefore, the tool responsible for performing weaving is called weaver.

3 Action-Oriented Exception Handling

In most programming languages, exception handling is inherently associated with system structuring concepts [17]. For instance, Java exception handling contexts are blocks of statements within method declarations and exceptions are objects defined by classes extending the class Exception. The exceptions that a method may signal define exit points for its execution (similarly to its result) that are only used when an error occurs. If an exception is raised within a method and no handlers for that exception are available, the exception is propagated to the caller method.

Page 845

In this paper, we use the concept of action to structure the concurrent execution of software systems. An action consists of a set of participants, units of computation such as threads or processes, that cooperate in order to achieve a common goal. The concept of Coordinated Atomic Actions (CA actions) [22] is employed to structure fault-tolerant concurrent systems in an action-oriented manner. CA actions provide a conceptual framework for dealing with different kinds of concurrency and achieving fault-tolerance by extending and integrating two complementary concepts: atomic actions [4] and ACID transactions [9]. Atomic actions are used to control cooperative concurrency and to implement coordinated error recovery. ACID transactions guarantee consistent access to shared objects.

A CA action consists of a set of participants cooperating inside it and a set of objects accessed by them (Figure 1). In a CA action, participants access shared objects that have the ACID properties. A CA action may terminate normally, in which case it produces a normal outcome and commits transactions on shared objects. If one or more exceptions are raised by action participants during action execution, all the participants are involved in coordinated exception handling. If two or more exceptions are concurrently signaled, an exception resolution scheme is used to combine these exceptions into a single one that represents all the exceptions signaled. If handling is successful, the action completes normally. Otherwise, an exception is propagated and responsibility for recovery is passed to the caller of the action. In this case, transactions on all shared objects are aborted. Actions may be nested in order to define different exception handling contexts. In the example of Figure 1, a nested action Action2 is created within the action Action1.

Figure 1: A CA action with three participants and a nested CA action.

4 Framework Design

ACE was built with the goal of complementing the EHS of Java with action-oriented exception handling, more specifically, with the concept of CA actions. In order to achieve this goal and to produce a reusable implementation, framework development has been guided from the beginning by the following design directives.
The concept of action should be explicit to application programmers. Application code based on ACE should employ the concept of action explicitly. This approach is adopted by the exception handling mechanism proposed by Garcia et al [7]. As mentioned in Section 3, exception handling is closely related to system structure and, in the case of concurrent exception handling, with constructs related to concurrent execution. Trying to make action-oriented exception handling transparent to application code may have a negative impact on system maintenance and understandability.

Page 846

Framework hotspots should be object-oriented. Framework users should only have to deal with object-oriented concepts. Although the framework is implemented as a combination of aspects, classes, and interfaces, application programmers should not need to know AspectJ or anything related to aspect-oriented programming, in order to use it. This directive makes framework usage easier for Java programmers and users of other object-oriented reusable implementations of coordinated exception handling.

The implementation of ACE consists of five main concerns related to the basic requirements of action-oriented exception handling and to some important non-functional requirements. The "core" framework comprises two concerns: action structuring (Section 4.1) and coordinated exception handling (Section 4.2). The remaining three concerns implement non-functional requirements: distribution (Section 4.3), preemptive abortion (Section 4.4), and transaction interface. The following subsections describe these points in detail, except for transaction interface, which is addressed elsewhere [15], [19]. In order to better assess our approach, throughout this section, we compare ACE to the purely object-oriented framework devised by Zorzo and Stroud [23] for building dependable multiparty interactions. The authors have used this framework, which we call DMI in the rest of this section, to implement the concept of CA actions.

4.1 Action Structuring

The action structuring concern makes it possible to organize concurrent systems as actions where participants cooperate in order to achieve a certain goal. In our approach for action structuring, participants are built by implementing the Participant interface. This interface defines only one method, execute(), that implements the basic functioning of the participant. It takes as argument an object of type Transactional which is shared by all action participants. The Transactional interface is defined by ACE and used for accessing objects in a transactional manner. Implementations of this interface should be provided by framework users.

Actions are represented by the Action interface, which extends Participant. This interface and a ready-to-use implementation are provided by ACE and implement the basic mechanisms for action structuring.

Implementation of the action structuring concern is complemented by an aspect, ActionStructuring. By means of inter-type declarations, this aspect makes Participant a subtype of Runnable, the interface provided by Java for defining objects that run in their own threads. Furthermore, it adds a new method to Participant, run(), that is called when a thread is started and is responsible for invoking execute() in each participant.

ActionStructuring is also responsible for managing references between actions and their participants. The following pointcut is used for this means. It selects all calls to the addParticipant() method, defined by Action.

Page 847



    01: pointcut participantAddition(Action cp, String id, 
    02:   Participant part): target(cp) && args(id, part) && 
    03:   execution(* *..Action.addParticipant(
    04:     String,Participant));

The addParticipant() method includes a new participant in an action. The pointcut above captures the moment in which this method is executed, as well as some contextual information. This information consists of (i) the Action object on which the method was invoked (target(cp)), and (ii) the identifier for the participant being added, as well as the participant itself (args(id, part)) (lines 1 and 2). When this pointcut is reached, an advice is executed after it, setting cp as the action part belongs to. This information is stored by Participant in a new field introduced by an inter-type declaration.

Creation and execution of an action is very simple. The following code snippet creates an action a1, comprising two participants, p1 and p2, and executes it.


    01: Participant p1 = new ParticipantImpl(); 
    02: Participant p2 = new ParticipantImpl(); 
    03: ActionFactory fac = ActionFactory.getInstance(); 
    04: Action a1 = fac.createAction("a1", 2000); 
    05: Transactional shared = new TransactionalImpl("An action");
    06: a1.addParticipant("Participant1", p1); 
    07: a1.addParticipant("Participant2", p2); 
    08: a1.execute(shared); 

Lines 1 and 2 create two participants, p1 and p2. Line 4 creates a new action with identifier a1 and which will wait at most 2000ms for participants to complete their execution. Actions are started by calling the execute() method on the action object (line 8). This method starts each participant in a new thread, passing the shared objects (line 5) as arguments to each of them.

Discussion

Part of the ActionStructuring aspect could be implemented as a class from which all participants would inherit. We have avoided this approach, however, because Java only allows single inheritance and user applications might require participants to extend a certain class. Moreover, it is argued by some authors [6], [12] that code inheritance promotes very strong coupling between classes, and that interfaces, together with aggregation, should be used whenever possible. In our approach, all the extra functionalities required by concurrent exception handling that would otherwise be implemented in a superclass are provided by aspects.

The use of aspects for implementing this concern helped making ACE easier to use, since participant classes defined by user applications need only to implement an interface. Furthermore, our aspect-based implementation completely encapsulates thread management-related issues. For instance, in the DMI framework, the threads on which each participant will be executed must be explicitly created and started by framework users. In spite of this, the aforementioned benefits could also be achieved by employing wrappers [6] for participants, in a purely object-oriented solution. These wrappers would be responsible for executing participants in new threads and maintaining references to the object representing the action. Hence, the aspect-based implementation did not present advantages over existing object-oriented solutions.

Page 848

4.2 Coordinated Exception Handling

This concern enriches action structuring with concurrent exception handling. It is implemented by the CoordinatedExceptionHandling aspect. This aspect requires that an exception handler class be created for each class defining a participant, in a given application. Exception handler classes implement the ExceptionHandler interface, which defines a single method, handleExceptions(), responsible for handling one or more exceptions.

The CoordinatedExceptionHandling aspect defines a pointcut, participantExecution, that intercepts all calls to the execute() method of objects of type Participant that are not of type Action. Two advice are associated with this pointcut. The first one creates, for each participant in an action, an instance of the corresponding exception handler class and associates this object with the participant. If no exception handler class is available or instantiation fails, an empty exception handler that simply re-throws any exceptions received is associated to the participant. Participant and exception handler classes are matched by adopting a naming convention to their definition.

The other advice associated to the participantExecution pointcut catches and records any exceptions signaled by participants. The following code snippet presents part of this advice.


    01: void around(Participant p) : participantExecution(p) { 
    02: p.setExceptionalResult(null); 
    03: try { 
    04: proceed(p); 
    05: } catch (Exception e) { 
    06: p.setExceptionalResult(e); } 
    07: }

The proceed() (line 4) statement is defined by AspectJ and can only be used in around advice. It resumes the execution of the intercepted code (in this example, the execute() method of Participant) and, after it finishes, returns to the advice, in the line following the proceed() statement. In the example above, if no exceptions are raised, execution is finished with no additional behavior being introduced. Otherwise, the exception is caught and stored in the participant itself, by calling the setExceptionalResult() (line 6). This method is introduced in Participant by an inter-type declaration.

CoordinatedExceptionHandling also defines a pointcut, actionExecution, that intercepts calls to the execute() method of objects of type Action. The following code snippet presents an advice associated to this pointcut.

Page 849


    01: void around(Action cp) 
    02:     throws Exception : actionExecution(cp) { 
    03:  try { 
    04:     proceed(cp);
    05:     this.performExceptionHandlingIfNecessary(cp); 
    06:  } catch (Exception e) { 
    07:    cp.setActionExceptionalResult(e); 
        08: throw e; 
        09: } 
        10: } 

The performHandlingIfNecessary() method (line 5) called after the proceed()statement (line 4) checks if any of the participants signaled an exception during its execution. If none did, the method returns and the action terminates. Otherwise, it collects all the exceptions signaled by action participants and initiates exception resolution. Many different schemes are possible for exception resolution. The one adopted by ACE, in conformance to Java´s EHS, assumes that exceptions are defined by classes and chooses the most specific exception class that is a supertype of the types of all exceptions signaled by participants.

Figure 2 presents a simple exception class hierarchy used to illustrate how exception resolution works in ACE. If, during the execution of an action, two participants simultaneously signal exceptions of types E3 and E4, exception resolution will create a new exception of type E5, since it is the most immediate superclass of both E3 and E4. If, on the other hand, exceptions of types E, E3, and E4 are signaled at the same time, the resolved exception will be of type E, because it is a superclass of both E3 and E4, and because we assume that the superclass relation is reflexive.

Figure 2: A simple exception class hierarchy.

After exception resolution finishes, the resolved exception is delivered to all participants and exception handling is initiated. For each participant, a new thread is created and the handleExceptions() method is called on the associated exception handler. This method takes the resolved exception as argument. Furthermore, the exceptions signaled by the participants during normal execution are also supplied as additional contextual information. Exception handling terminates normally if the participants do not signal any exceptions, while handling the resolved exception. Otherwise, a FailureException is signaled, indicating that the action as a whole has failed and system state may be inconsistent.

Page 850

Discussion

The implementation of coordinated exception handling by an aspect did not require any modifications to the code of the action structuring concern. Implementing similar features in a purely object-oriented language would either require the use of code inheritance, the solution adopted by the DMI framework, which we reject, or the construction of additional wrappers. Both cases require either the application code or the implementation of the action structuring concern to be modified. Therefore, the aspect-based solution promotes better separation of concerns.

When an exception is raised, it is useful to collect some information regarding the context in which it was raised. This contextual information is then made available to exception handlers, in order to increase the effectiveness of exception handling. Relevant information usually includes the values of local variables declared within the raising method. Unfortunately, it was not possible to collect this contextual information with the current version of AspectJ (1.1), since it does not allow the definition of pointcuts related to local variables.

4.3 Distribution

ACE has been devised with the goal of supporting development of large scale fault-tolerant object-oriented distributed systems. However, action structuring and coordinated exception handling concerns implicitly assume that all the participants of an action share the same address space and processing unit. This conflict between goal and assumptions is resolved by the distribution concern. We have used Java RMI [5] as the underlying distribution technology.

First Attempt: Aspects

In order to introduce the distribution concern, we began by implementing distribution for server-side objects. We first tried the approach described by Soares et al [19] for adding distribution to object-oriented systems by means of aspects. First, a new interface, DistributedParticipant, was created. This interface defines all the methods implemented by participants that may be accessed remotely. The methods in this interface should specify the RemoteException exception in their throws clauses because Java requires it from all methods that might be called remotely. DistributedParticipant extends Remote, the interface used by RMI to indicate that an object is remote, and includes both the methods defined by the Participant interface and the methods added to Participant by inter-type declarations. We then created a new aspect, Distribution, and made Participant a subtype of DistributedParticipant by means of an inter-type declaration.

This approach did not work, however, because classes defining remote objects must directly implement their remote interfaces. The solution described above works in the example application presented by Soares et al [19] because the classes defining remote objects implement the remote interface directly.

Page 851

In the implementation of the distribution concern, the implementation classes are not known beforehand (because these are application-specific participants). In order to bypass the limitation described in the previous paragraph, we specified an inter-type declaration stating that all implementations of the Participant interface should also implement the interface DistributedParticipant. In this manner, classes defining remote objects would directly implement the remote interface. This is defined as follows.


    01: declare parents: (Participant+ && !Participant) 
    02: implements DistributedParticipant;

This solution works fine when no aspects define inter-type declarations affecting Participant. However, this is not the case for our implementations of the action structuring and coordinated exception handling concerns. Hence, an alternative solution should be found.

A Solution Based on Aspects and an OO Design Pattern

We have adapted the work of Alves and Borba [1] to introduce distribution in ACE. This work proposes a design pattern, called Distributed Adapters Pattern (DAP), for implementing distribution in layered systems. This pattern isolates distribution-related code in two classes, called distributed adapters. Our implementation of distributed adapters defines five roles: local interface, local object, remote interface, client-side adapter, and server-side adapter.

The local interface specifies services to be made remote. In ACE, Participant, including and all the methods added to it by means of inter-type declarations, is a local interface. Instances of classes implementing the local interface are called local objects. The remote interface DistributedParticipant defines the same methods as the local interface, but the exception RemoteException is specified in the throws clause of each of them, since they will be called remotely. The client-side adapter is defined by the DistributedClient class. It implements the local interface and maintains a remote reference to the server-side adapter. The client-side adapter makes distribution transparent to clients by delegating method invocations received from them to the server-side adapter. The server-side adapter is defined by the DistributedParticipantImpl class. It implements the remote interface and maintains a reference to the local object. This adapter delegates received remote method invocations to the local object.

The problem with using the distributed adapters pattern in a purely object-oriented manner is that adapter creation must be invoked explicitly by application code, even if factories [6] are used to hide the actual instantiation process. We have employed aspects to try to alleviate this limitation of the object-oriented approach. A new aspect has been created, DistributionWithDAP, that localizes the solution to this problem. Our approach is divided in two parts: server-side and client-side distribution.

Page 852

Server-Side Distribution

A simple implementation pattern adopted by many distributed applications created with Java RMI consists in placing the code responsible for instantiating and publishing remote objects in the main() method of one or more server classes. In our approach, we assume that this pattern is adopted by framework users. However, instead of creating distributed objects and publishing them, the implementation of such methods should only instantiate the local objects to be made remote. The DistributionWithDAP aspect checks, for each instantiated local participant, if a corresponding remote participant has been published. This task is performed by a before advice associated to the callToMain pointcut, which intercepts calls to the main() method of a participant. The following code snippet presents part of this advice.


    01: try { 
    02:   String addr = addresses.getProperty(participantId); 
    03:   if(addr!=null && addr.trim().length() > 0) { 
    04:     Remote r = Naming.lookup(addr); 
    05:     toBePublished[i] = false; 
    06:   } 
    07: }catch(NotBoundException nbe) { toBePublished[i] = true; }

The address for one of the participants, addr, is obtained (line 2) by means of a list of properties loaded at aspect initialization. If a participant has already been published at addr (line 4), it should not be published again (line 5). Otherwise, a NotBoundException is raised and caught, indicating that the participant was not published yet (line 7).

The other advice associated to the callToMain pointcut complements the one presented above and binds unpublished remote participants to the supplied identifier, after the execution of the main() method. Remote participants are created when participant classes are instantiated. A pointcut, callToConstructor, selects all calls to constructors of classes implementing the Participant interface. An advice creates objects of type DistributedParticipant when callToConstructor is reached. The following code snippet presents this advice.


01: void around(Participant p) : callToConstructor(p) { 
02:   proceed(p); 
03:   if(!alreadyInstantiated(p)) { 
04:     DistributedParticipant dpi = null; 
05:     dpi = new DistributedParticipantImpl(p); 
06:     storeDistributedParticipant(dpi); 
07:   } 
08: } 

First, the participant is created (line 2). The check in line 3 avoids the creation of more than one object of type DistributedParticipant for the same participant. This may happen due to calls to superclass constructors. In line 5, the distributed participant is actually created and in line 6 it is stored, so that it can be published later.

Page 853

Client-Side Distribution

Transparency at the client-side is achieved by guaranteeing that remote references to distributed objects are acquired as if they were local. Our approach consists of requiring that local instances of the remote participants be created at the client-side (as if it were not a distributed application), intercepting the moment in which a local participant is added to an action, obtaining the remote reference corresponding to the local object being added, and adding the remote reference, instead of the local one.

The participantAddition pointcut defined by the aspect ActionStructuring intercepts calls to the addParticipant() method of the Action interface. Hence, we moved this pointcut to an abstract aspect and made both DistributionWithDAP and ActionStructuring extend this aspect. A new advice, associated to participantAddition, was then added to DistributionWithDAP. The code for this advice is presented below.


01: void around(String id, Participant p, Action ac) : 
02:     participantAddition(id, p, ac) { 
03:   String addr = addresses.getProperty(id);
04:   try { 
05:     Remote r = Naming.lookup(addr);
06:     if(!validParticipant(r)) proceed(id, p, ac); 
07:     else proceed(id, new DistributedClient( 
08:          (DistributedParticipant)r), ac); 
09:   } catch(Exception e) { proceed(id, p, ac); } 
10: } 

The identifier of the participant being added is used to obtain the address of the corresponding remote object (line 3). This address is used to obtain the remote reference (line 5). If it is not possible to obtain a valid remote reference, the local participant is used instead (line 6). Otherwise, a new client-side adapter of type DistributedClient is created to encapsulate the remote reference. This adapter is then added to the action as a new participant (lines 8 and 9). If any error occurs during this process, the local participant is used (line 9).

Discussion

The implementation of the distribution concern described in this section is clearly superior to the purely object-oriented version. Complete transparency to application code could be achieved due to the use of aspects. In order to use the distributed version, only a small amount of Java code responsible for creating the distributed objects is required. Furthermore, application code used to build distributed applications makes no references to distribution-related issues. Hence, if a distributed application needs to be used in a local setting, no modifications are required. It is simply a matter of not weaving the DistributionWithDAP aspect.

Comparing our approach to the one presented by Soares et al [19] is difficult because we have not evaluated the use of aspects together with distributed adapters in more general contexts. Although the solution by Soares et al could not be employed to implement distribution in ACE, we are still not aware of all the limitations of our own approach. Hence, claiming that one is more or less general than the other would be inadequate.

Page 854

The main cause for the difficulties outlined in this section is the fact that the current version of AspectJ (1.1) does not support the addition of new exceptions to the throws clause of a method. Hence, it was necessary to define another remote interface for distributed participants. This problem has been reported elsewhere [15], [19] and an extension to AspectJ that effectively solves it has been suggested [19].

4.4 Preemptive Abortion

When an exception is signaled by one of the participants of an action, all the other participants should abort their executions so that exception handling can take place. This makes execution faster and does not allow errors to spread throughout the system. Usually, implementations of CA actions based on Java use the features provided by the language for thread interruption, in order to implement participant abortion. This is the solution adopted by the DMI framework.

The problem with Java´s features for thread interruption is that they do not actually guarantee that a thread will be interrupted. Thread interruption is requested by invoking a method that marks the thread as interrupted. Actual interruption only occurs if an interrupted thread blocks waiting for locks or I/O operations. In order to guarantee that a participant will not go on executing when it should abort, we have implemented an aspect, PreemptiveAbortion, that aborts the execution of a participant almost immediately.

Participants of an action should be aborted when at least one of them has signaled an exception. As described in Section 4.2, any exception signaled by a participant is recorded by calling the setActionExceptionalResult() method. The pointcut storingRaisedExceptions selects all invocations to this method. The following advice, associated to this pointcut, is responsible for notifying the other action participants that their executions should be aborted.


01: void around(Participant p, Exception e) :
02:     storingRaisedException(p, e) { 
03:   proceed(p, e);
04:   Action ac = p.getEnclosingAction(); 
05:   notifyParticipants(ac, p); 
06: }

First, the signaled exception is recorded (line 3). The Action object corresponding to the action of which p (line 1) is a participant is then obtained by calling the getEnclosingAction() method (line 4). Finally, all the participants within the action ac (line 4) are notified (line 5).

Preemptive abortion is implemented by checking if, at any moment during the execution of a participant, it was notified that it should abort. The following pointcut selects all the statements within the execute() method of a class that implements Participant, but not Action.

    01: pointcut participantsExecuteMethod(Participant p):
    02:    withincode(* *..Participant+.execute(..)) && target(p)
    03:    && !withincode(* *..Action+.execute(..));

Actual abortion is implemented by a simple advice, associated to the pointcut above, which checks, after each statement, if execution should be aborted. In case it should, this advice raises a special runtime exception, AbortionException.

Page 855

Discussion

The implementation of the preemptive abortion concern nicely showcased the possibilities of using aspect-oriented programming together with object-oriented programming. It would not be feasible to implement this concern transparently to user applications without employing AspectJ, since it requires intercepting all the statements in a method body. This level of granularity is too low for typical solutions for interception, such as wrappers and proxies [5].

Since the PreemptiveAbortion aspect uses inter-type declarations to introduce some new methods in Participant, the implementation of the distribution concern needed to be extended in order to accommodate these changes. This could be achieved, however, without modifying any of the aspects, classes, or interfaces implementing either distribution or preemptive abortion. A new aspect, DistributedPreemptiveAbortion, was created which complements the client and server adapters with the new methods introduced by PreemptiveAbortion.

5 Conclusions

In this paper, we have described ACE, an aspect-based framework for implementing coordinated exception handling in distributed object-oriented systems. Our main contribution was to assess the adequacy of using AspectJ to implement a reusable infrastructure for coordinated exception handling. To the best of our knowledge, all the works published on the use of aspects to structure exception handling refer exclusively to sequential exception handling. Moreover, we have compared ACE to a purely object-oriented approach to implementing coordinated exception handling. Strong and weak points of both our approach and AspectJ have been pointed out.

The aspect-based implementation of the action structuring concern did not present advantages over an object-oriented one, mainly due to the design directives described in Section 4. In spite of this, our results lead us to conclude that our aspect-based reusable implementation for coordinated exception handling is superior to a purely object-oriented one. Coordinated exception handling, distribution, and preemptive abortion have been designed so that these concerns can be added to an action-structured application in a non-intrusive manner. This level of transparency could not be achieved by employing only object-oriented techniques, such as design patterns.

In order to assess our framework from a usability viewpoint, we intend to build a large case study based on ACE, employing different communication technologies, such as Enterprise Javabeans [20]. This will allow us to better understand its advantages and, most of all, its limitations. Furthermore, it will clarify how our design decisions influence the overall framework usability and provide suggestions for features to be added in the future.

Another future work consists of extending ACE with features useful for building component-based systems. This is an ongoing work that is in its initial stages. We are currently studying the implications of using an aspect-based framework such as ACE for building applications whose computational model requires black-box visibility.

Page 856

Acknowledgements

We would like to thank the anonymous referees for helping to improve this paper. We would also like to thank Paulo Borba for helping us to understand how aspects interact, and Alexandra Barros and Lásaro Camargos for the comments and suggestions on early drafts of the paper. Fernando Castor is supported by FAPESP/Brazil under grant no. 02/13996-2. Cecília Rubira is supported by CNPq/Brazil, under grant no. 351592/97-0.

References

[1] Alves, V. and Borba, P. (2001) "Distributed Adapters Pattern: A Design Pattern for Object-Oriented Distributed Applications". In: Proceedings of the First Latin American Conference on Pattern Languages Programming (SugarLoafPLoP), Rio de Janeiro, Brazil.

[2] Anderson, T., and Lee, P. (1990) Fault Tolerance: Principles and Practice, 2nd Edition, Prentice-Hall.

[3] Buhr, P. A. and Mok, W. Y. R. (2000) "Advanced Exception Handling Mechanisms". In: IEEE Transactions on Software Engineering, 26(9), pp 820 - 836, IEEE Computer Society Press.

[4] Campbell, R. and Randell, B. (1986) "Error Recovery in Asynchronous Systems". In: IEEE Transactions on Software Engineering, SE-12(8), pp. 811 - 826, IEEE Computer Society Press.

[5] Campione, M., Walrath, K., and Huml A. (2000) The Java Tutorial: A Short Course on the Basics, 3rd Edition, Addison-Wesley.

[6] Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1994) Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley.

[7] Garcia, A., Beder, D., Rubira, C. (1999) "An Exception Handling Mechanism for Developing Dependable Object-Oriented Software Based on a Meta-Level Approach". In: Proceedings of the 10th IEEE International Symposium on Software Reliability Engineering (ISSRE´99), USA, pp. 52 - 61, IEEE Computer Society Press.

[8] Goodenough, J. B. (1975) "Exception Handling, Issues and a Proposed Notation". In: Communications of the ACM, 18(12), pp. 683 - 696.

[9] Gray, J. and Reuter, A. (1993) Transaction Processing: Concepts and Techniques, Morgan Kaufmann.

[10] Hejlsberg, A., Wiltamuth, S., and Golde, P. (2003) The C# Programming Language, Addison-Wesley, Reading, MA, USA.

[11] Koening, A. and Stroustrup, B. (1990) "Exception Handling for C++", In: Journal of Object-Oriented Programming, 3 (2) , pp. 16 - 33.

[12] Hollub, A. (2003) "Why Extends is Evil", In: JavaWorld.com. Available at: http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox_p.html

Page 857

[13] Kiczales, G., Hilsdale, E., Hugunin, J., Kersten., M., Palm, J., and Griswold, W. G. (2001) "Getting Started with AspectJ". In: Communications of the ACM, 44(10), pp. 59 - 65.

[14] Kiczales, G., Lamping, J., Mendhekar, A., Maeda, C., Lopes, C. V., Loingtier, J.-M., and Irwin, J. (1997) "Aspect-Oriented Programming". In: Proceedings of the 11th European Conference on Object-Oriented Programming (ECOOP´97), Finland, LNCS 1241, Springer-Verlag, pp. 220 - 242.

[15] Kienzle, J., Guerraoui, R. (2002) "AOP: Does it Make Sense? The Case of Concurrency and Failures". In: Proceedings of the European Conference on Object-Oriented Programming (ECOOP´02), Málaga, Spain, LNCS 2374, Springer-Verlag, pp. 37 - 61.

[16] Lippert, M. and Lopes, C. V. (2000) "A Study on Exception Detection and Handling Using Aspect-Oriented Programming". In: Proceedings of the 22nd International Conference on Software Engineering, Limerick, Ireland, pp 418 - 427.

[17] Romanovsky, A. and Kienzle, J. (2001) "Action-Oriented Exception Handling in Cooperative and Competitive Object-Oriented Systems". In: Romanovsky A., Dony, C., Knudsen, J. L., Tripathi, A. (Eds.), Advances in Exception Handling Techniques, LNCS 2022, Springer-Verlag, pp. 147 - 164.

[18] Romanovsky, A., Periorellis, P., and Zorzo, A. F. (2003) "Structuring Integrated Web Applications for Fault Tolerance". In: Proceedings of the 6th IEEE International Symposium on Autonomous Decentralized Systems (ISADS'03), Pisa, Italy, pp 99 - 106.

[19] Soares. S., Laureano, E., Borba, P. (2002) "Implementing Distribution and Persistence Aspects with AspectJ". In: Proceedings of the 17th ACM Conference on Object-Oriented Programming, Systems, Languages and Applications (OOPSLA'02), pp 174 - 190.

[20] Sun Microsystems. (2002) Enterprise Javabeans Specification v2.1 - Proposed Final Draft.

[21] Tartanoglu. F., Issarny, V., Romanovsky, A., and Levy, N. (2003) "Coordinated Forward Error Recovery for Composite Web Services". In: Proceedings of the 22nd IEEE Symposium on Reliable Distributed Systems (SRDS'03), Florence, Italy, pp 167 - 176.

[22] Xu, J., Randell, B., Romanovsky, A., Rubira, C., Stroud, R. J., Wu, Z. (1995) "Fault Tolerance in Concurrent Object-Oriented Software through Coordinated Error Recovery". In: Proceedings of the 25th International Symposium on Fault-Tolerant Computing, Pasadena, California, USA, p. 499 - 509.

[23] Zorzo, A. F. and Stroud, R. J. (1999) "A Distributed Object-Oriented Framework for Dependable Multiparty Interactions". In: Proceedings of the 14th ACM Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA'99), Denver, Colorado, USA, p. 435 - 446.

Page 858