Implementing Atomic Actions in Ada 95.
AbstractAtomic actions are an important dynamic structuring technique that aid the construction of fault-tolerant concurrent systems. Although they were developed some years ago, none of the well-known commercially-available programming languages directly support their use. This paper summarizes software fault tolerance techniques for concurrent systems, evaluates the Ada 95 programming language from the perspective of its support for software fault tolerance, and shows how Ada 95 can be used to implement software fault tolerance techniques. In particular, it shows how packages, protected objects, requeue, exceptions, asynchronous transfer of control, tagged types, and controlled types can be used as building blocks from which to construct atomic actions with forward and backward error recovery, which are resilient to deserter tasks and task abortion.
Introduction
Software fault tolerance is often classified according to whether it is static (mask-
ing) or dynamic. With static redundancy, several versions of a software component
are written and each version executes in response to all requests; voting
is performed on the output to determine which result to use. It is static because
each version of the software has a fixed relationship with every other version, and
the voter; and because it operates whether or not faults have occurred. With
dynamic redundancy, the redundant components only come into operation when
an error has been detected.
Dynamic fault tolerance has four constituent phases (Anderson and Lee,
1990).
1. detection - Faults of significance will eventually manifest themselves
in the form of an error; no fault tolerance scheme can be utilised until that
error is detected.
2. Damage confinement and assessment - When an error has been detected,
a decision must be made on the extent to which the system has been
corrupted; the delay between a fault occurring and the manifestation of
the associated error means that erroneous information could have spread
throughout the system.
3. recovery - Error recovery techniques aim to transform the corrupted
system into a state from which it can continue its normal operation
(perhaps with degraded functionality).
4. Fault treatment and continued service - An error is a symptom of a fault;
although the damage may have been repaired, the fault may still exist
and, therefore, the error may recur unless some form of maintenance is
undertaken.
This paper is primarily concerned with dynamic redundancy techniques and,
in particular, damage confinement and error recovery. For sequential systems,
damage confinement is well understood; techniques such as modular programming
and object-oriented encapsulation (within the context of a strongly-typed
programming language) enable faults to be confined. Judicious placement of
acceptance tests or assertions allow errors to be detected before damage can be
propagate. Similarly, techniques such as exception handling(Goodenough, 1975)
(forward error recovery) and recovery blocks(Horning et al., 1974) (backward
error recover) allow error recovery to be performed according to whether the
fault was anticipated or not.
For concurrent systems, the position is not so clear cut. Although techniques
such as conversations(Campbell and Randell, 1986) and atomic actions(Lomet,
were developed some time ago, few of the main-stream languages or
operating systems provide direct support (Burns and Wellings, 1990). In-
stead, languages just as Concurrent Pascal has been used as the basis for
experimentation(Kim, 1982), or a set of procedural extensions or object extensions
have been produced. Arjuna uses the latter approach to provide a
transaction-based toolkit for C++(Shrivastava et al., 1991).
Arguably, high-level support is not needed and the required functionality
can be programmed with lower-level primitives. For example, some attempts
have been made to program conversations in Ada 83(Burns and Wellings,
1989)(Clematis and Gianuzzi, 1993) (Romanovsky and Strigini, 1995); however,
these were severely hampered by the lack of suitable language support. For in-
stance, Romanovsky and Strigini (1995) only allow parallelism to exist inside a
conversation; the approach is not appropriate if a collection of pre-existing tasks
wish to participate collectively in a conversation. None of these approaches address
how to structure atomic actions with both forward and backward error
recover in Ada 95.
The goal of this paper is to show that atomic actions can be programmed
in Ada 95(Intermetrics, 1995). Section 2 reviews the requirements for atomic
action and Section 3 briefly describes how backward and forward error recovery
can be undertaken. Section 4 describes the new features of Ada 95 that are
required to implement atomic actions. Section 5 then shows how these features
can be used to program: simple actions, actions with backward error recov-
ery, actions with forward error recovery, nested actions, and actions which are
resilient to deserters. Section 6 gives a simple example, Section 7 considers
distributed systems issues and, finally, conclusions are presented in Section 8.
Atomic Actions
One of the main motivations for introducing concurrent processes into a system
is that they enable parallelism in the real world to be reflected in application
programs. This enables such programs to be expressed in a more natural way
and leads to the production of more reliable and maintainable systems. However,
concurrent processes create many new problems which did not exist in the purely
sequential program. In particular, consideration has to be given to the way in
which groups of cooperating concurrent processes should be structured in order
to coordinate their activities. For example, withdrawal from a bank account may
involve a ledger process and a payment process in a sequence of communications
to authenticate the drawer, check the balance and pay the money. Furthermore,
it may be necessary for more than two processes to interact in this way to
perform the required action. In all such situations, it is imperative that the
processes involved see a consistent system state. With concurrent processes, it
is all too easy for groups of processes to interfere with one other.
Atomic actions have been proposed as a dynamic mechanism for controlling
the joint executing of a group of processes such that their combined operation
appears as an indivisible action.
There are several almost equivalent ways of expressing the properties of an
atomic action (Lomet, 1977) (Randell et al., 1978).
1. An action is atomic if the processes performing it are not aware
of the existence of any other active process, and no other active
process is aware of the activity of the processes during the time
the processes are performing the action.
2. An action is atomic if the processes performing it do not communicate
with other processes while the action is being performed

3. An actions is atomic if the processes performing it can detect no
state change except those performed by themselves, and if they
do not reveal their state changes until the action is complete.
4. Actions are atomic if they can be considered, so far as other
processes are concerned, to be indivisible and instantaneous,
such that the effects on the system are as if they were interleaved
as opposed to concurrent.
Although an atomic action is viewed as being indivisible, it can have an internal
structure. To allow modular decomposition of atomic actions, the notion of a
nested atomic action is introduced. The processes involved in a nested action
must be a subset of those involved in the outer level of the action. If this were
not the case, a nested action could smuggle information concerning the outer-
level action to an external process. The outer-level action would then no longer
be indivisible.
2.1 Requirements for Atomic Actions
If a programming language is to be capable of supporting atomic actions then it
must be possible to express the requirements necessary for their implementation.
These requirements are independent from the notion of a process and the form
of inter-process communication provided by a language (Jalote, 1985). They
are:
1. Well-defined boundaries
Each atomic action should have a start, end and a side boundary. The
start boundary is the location in each process involved in the atomic action
where the action is deemed to start. The end boundary is the location in
each process involved in the atomic action where the action is deemed to
end. The side boundary separates those processes involved in the atomic
action from those in the rest of the system.
2. Indivisibility
An atomic action must not allow the exchange of any information between
the processes active inside the action and those outside (resource managers
excluded). If two atomic actions do share data then the value of that data
after the atomic actions is determined by the strict sequencing of the two
actions in some order.
There is no implied synchronisation at the start of an atomic action. Processes
can enter at different times. However, there is an implied synchronisation
at the end of an atomic action; processes are not allowed to leave
the atomic action until all processes are willing and able to leave.
3. Nesting
Atomic actions may be nested as long as they do not overlap with other
atomic actions. Consequently, in general, only strict nesting is allowed.
4. Concurrency
It should be possible to execute different atomic actions concurrently.
One way to enforce indivisibility is to run atomic actions sequentially.
However, this could seriously impair the performance of the overall system
and therefore should be avoided. Nevertheless, the overall effect of
running a collection of atomic actions concurrently must be the same as
that which would be obtained from serialising their executions.
5. Recovery
As it is the intention that atomic actions should form the basis of damage
confinement, they must allow recovery procedures to be programmed.
Executing an atomic action requires the participating processes to coordinate
they activities. The imposed synchronisation on the action is as follows. Processes
entering the action are not blocked. A process is only blocked inside
the action if it has to wait for a resource to be allocated, or if it attempts to
communicate with another process inside the action and that process is either:
active in the action but not in a position to accept the communication; or is
not, as yet, active in the action.
may leave the action only when all processes active in the action
wish to leave. Hence, it is possible for a subset of the named processes to enter
the action and subsequently leave (without recourse to any interactions with
the missing processes). This facility is deemed to be essential in a real-time
system where deadlines are important. It solves the deserter problem where all
processes are held in an action because one process has not arrived. This will
be considered along with error recovery in the next two sections.
3 Recoverable atomic actions
This section considers atomic actions with both forward and backward error
recovery.
3.1 Atomic actions and backward error recovery
When backward error recovery is applied to groups of communicating pro-
cesses, it is possible for all the processes to be rolled back to the start of their
execution. This is the so called domino effect. The problem occurs if there is no
consistent set of recovery points or a recovery line. An atomic action provides
that recovery line automatically. If an error occurs inside an atomic action then
the processes involved can be rolled back to the start of the action and alternative
algorithms executed; the atomic action ensures that processes have not
passed any erroneous values through communication with processes outside the
action. When atomic actions are used in this way they are called conversations
(Randell, 1975).
With conversations each action statement contains a recovery block. For
example:
action A with
ensure <acceptance test>
by
primary module
else by
alternative module
else by
alternative module
else error
Other processes involved in the conversation declare their part in the action
similarly. The basic semantics of a conversation can be summarised as follows:
ffl On entry to the conversation, the state of a process is saved. The set of
entry points forms the recovery line.
Whilst inside the conversation, a process is only allowed to communicate
with other processes active in the conversation and general resource man-
agers. As conversations are built from atomic actions, this property is
inherited.
ffl In order to leave the conversation, all processes active in the conversation
must have passed their acceptance test. If this is the case then the
conversation is finished and all recovery points are discarded.
ffl If any process fails its acceptance test, all processes have their state restored
to that saved at the start of the conversation and they execute their
alternative modules. It is, therefore, assumed that any error recovery to
be performed inside a conversation must be performed by all processes
taking part in the conversation.
ffl Conversations can be nested, but only strict nesting is allowed.
ffl If all alternatives in the conversation fail then recovery must be performed
at a higher level.
It should be noted that in conversations, as defined by Randell (1975), all processes
taking part in the conversation must have entered the conversation before
any of the other processes can leave. This differs from the semantics described
here. If a process does not enter into a conversation, either because of tardiness
or because it has failed, then as long as the other processes active in the conversation
do not wish to communicate with it then the conversation can complete
successfully. If a process does attempt to communicate with a missing process
then it can either block and wait for the process to arrive or it can continue.
Adopting this approach has two benefits (Gregory and Knight, 1985):
1. It allows conversations to be specified where participation is not compulsory

2. It allows processes with deadlines to leave the conversation, continue and
if necessary take some alternative action.
Conversations have been discussed by Kim (1982) in the context of extensions
to Concurrent Pascal and Tyrrell and Holding (1986) and Jalote and
Campbell (1984) (1986) in the context of CSP.
Although conversations allow groups of processes to coordinate their recov-
ery, they have been criticised. One important point is that when a conversation
fails all the processes are restored and all enter their alternative modules. This
forces the same processes to communicate again to achieve the desired effect;
a process cannot break out of the conversation. This may be not what is re-
quired. Gregory and Knight (1985) point out that in practice when one process
fails to achieve its goal in a primary module through communication with one
group of processes, it may wish to communicate with a completely new group
of processes in its secondary module. Furthermore, the acceptance test for this
secondary module may be quite different. There is no way to express these requirements
using conversations. To overcome some of the problems associated
with conversations, Gregory and Knight (1985) have proposed an alternative
approach to backward error recovery between concurrent processes.
3.2 Atomic actions and forward error recovery
Although backward error recovery enables recovery from unanticipated errors,
it is difficult to undo any operation that may have been performed in the environment
in which the system operates. Consequently forward error recovery and
exception handling must also be considered. In this section, exception handling
between the concurrent processes involved in an atomic action is discussed.
With backward error recovery, when an error occurs all processes involved
in the atomic action participate in recovery. The same is true with exception
handling and forward error recovery. If an exception occurs in one of the processes
active in an atomic action then that exception is raised in all processes
active in the action. The exception is said to be asynchronous as it originates
from another process. The following is a possible Ada-like syntax for an atomic
action supporting exception handling.
action A with
- the action
exception
when exception_Y =>
- sequence of statements
when exception_Z =>
- sequence of statements
when others =>
raise atomic_action_failure;
With the termination model of exception handling, if all processes active in the
action have a handler and all handle the exception without raising any further
exception, then the atomic action completes normally. If a resumption model is
used, once the exception has been handled, the processes active in the atomic
action resume their execution at the point where the exception was raised.
With either model, if there is no exception handler in any one of the processes
active in the action or one of the handlers fails then the atomic action fails with
a standard exception atomic action failure. This exception is raised in all the
involved processes.
There are two issues which must be considered when exception handling is
added to atomic actions: resolution of concurrently raised exceptions and exceptions
in nested actions(Campbell and Randell, 1986). These are now briefly
reviewed.
Resolution of concurrently raised exceptions
It is possibly for more than one process active in an atomic action to raise different
exceptions at the same time. As Campbell and Randell (1986) point out,
this event is likely if the errors resulting from some fault cannot be uniquely
identified by the error detection facility provided by each component of the
atomic action. If two exceptions are simultaneously raised in an atomic action
then there may be two separate exception handlers in each process. It may be
difficult to decide which one should be chosen. Furthermore, the two exceptions
in conjunction constitute a third exception which is the exception which
indicates that both the other two exceptional conditions have occurred.
In order to resolve concurrently raised exceptions, Campbell and Randell
propose the use of an exception tree. If several exceptions are raised concurrently
then the exception used to identify the handler is that at the root of the smallest
subtree that contains all the exceptions (although it is not clear how to combined
any parameters associated with this exception). Each atomic action component
can declare its own exception tree; the different processes involved in an atomic
action may well have different exception trees.
Exceptions and internal atomic actions
Where atomic actions are nested, it is possible for one process active in an action
to raise an exception when other processes in the same action are involved in a
nested action. Figure 1 illustrates the problem.
When the exception is raised, all processes involved must participate in the
recovery action. Unfortunately, the internal action, by definition, is indivisible.
Exception
raised here
Action A
Nested action B
IM

Figure

1: An exception in a nested atomic actions
To raise the exception in that action would potentially compromise that indivis-
ibility. Furthermore, the internal action may have no knowledge of the possible
exception that can be raised.
Campbell and Randell (1986) have discussed two possible solutions to this
problem. The first solution is to hold back the raising of the exception until the
internal action has finished. This they reject because:
ffl In a real-time system the exception being raised may be associated with
the missing of a deadline. To hold back the recovery procedure may seriously
place in jeopardy the action's timely response.
ffl The error condition detected may indicate that the internal action may
never terminate because some deadlock condition has arisen.
For these reasons, Campbell and Randell allow internal actions to have a pre-defined
abortion exception. This exception is raised to indicate to the action
that an exception has been raised in a surrounding action and that the pre-conditions
under which the action was invoked are no longer valid. If such an
exception is raised, the internal action should invoke fault-tolerant measures to
abort itself. Once the action has been aborted, the containing action can handle
the original exception.
If the internal action cannot abort itself then it must signal an atomic action
failure exception. This may then be combined with the outstanding exception
so as to affect the choice of recovery performed by the surrounding action. If no
abortion exception is defined, the surrounding action must wait for the internal
action to complete. Alternatively, a default handler could be provided which
would raise the atomic action failure exception.
Ada 83, along with most concurrent programming languages, was unable to
support the full functionality of atomic actions(Burns and Wellings, 1989). Ada
95 does not support atomic actions directly but does provide a rich supply
of language primitives which potentially can be used to implement the same
functionality. In particular:
packages - provide encapsulation and information hiding
exceptions - provide a basic termination model of exception handling
protected objects - provide a monitor-like communication mechanism
asynchronous transfer of controls (ATC) - provides a mechanism by which
one task can asynchronous obtain the attention of another
tagged types - provide the framework from within which object-oriented programming
can be performed
controlled types - provide the mechanism by which finalisation code can be
associated with objects.
Packages and exceptions were available in Ada 83 and, therefore, will not be discussed
further. However, protected objects, ATC, tagged types and controlled
types are new and will be briefly described. For a full discussion on these aspects
of Ada 95 see Burns and Wellings (1995).
4.1 Protected Objects
A protected object in Ada 95 is similar in concept to a conditional critical
region(Hoare, 1972)(Brinch-Hansen, 1972) and a monitor(Hoare, 1974)(Brinch-
Hansen, 1973). Data which is to accessed in mutual exclusion is encapsulated
in a protected object. This data can only be accessed by procedures and entries
also declared in the protected object. Execution of a procedure or an entry
requires mutual exclusion access over the object. As with tasks in Ada, protected
objects may be declared as instances of a protected record type, or a single one-off
protected object (of anonymous type). For example, the following protected
object is a single instance of an anonymous protected record type which allows
mutually exclusive access to shared data.
protected Shared_Data is
- operations on the shared data
procedure
procedure Read(D: out
private
The_Data : Data := Some_Initial_Value;
- the encapsulated data
protected body Shared_Data is
procedure
begin
The_Data := D;
procedure Read(D : out Data) is
begin
The difference between a procedure and an entry in a protected object is as
follows. A procedure simply provides mutual exclusive access to the data. If
there is no other task active in the protected object, a call on the procedure will
gain immediate access to the data. An entry has an associated guard (called a
barrier). A call to a guarded entry will only be allowed if the guard evaluates
to true and there is no other task active in the protected object. The following
shows the above reader/writer example when a write must initialise the data
before it is read.
protected Shared_Data is
procedure
entry Read(D: out
private
The_Data
protected body Shared_Data is
procedure
begin
The_Data := D;
Data_Available := True;
- indicate that the data is available
when Data_Available is
begin
Inside a protected entry, the call can be requeued back onto the same entry or
another entry of the same (or different) protected object.
4.2 ATC
The Ada 83 selective entry call facility is extended in Ada 95 to allow a task to
execute a section of code whilst it is waiting for the entry (or timeout) to occur.
If the code finishes before the entry call is accepted (or the timeout expires) then
the call (or timeout) is cancelled. If the call is accepted (or timeout expires)
before the section of code finishes then the execution of the code is aborted.
The following illustrates the syntax:
select
Trigger.Event; - trigger is a protected object
optional sequence of statements to
- be executed after the event has been
received
then abort
abortable sequence of statements
4.3 Tagged Types and Object-Oriented Programming
Ada supports object-oriented programming through two complimentary mechanisms
which provide type extensions and dynamic polymorphism: tagged types
and class-wide types.
In Ada, a new type can be created from an old type and some of the properties
of the type changed using derived types. For example, the following declares
a new type and a subtype called Setting which has the same properties as the
Integer type but a restricted range. Setting and Integer are distinct and
cannot be interchanged:
type Setting is new Integer range 1 . 100;
New operations manipulating Setting can be defined; however no new components
can be added. Tagged types remove this restriction and allow extra
components to be added to a type. Any type that might potentially be extended
in this way must be declared as a tagged type. Because extending the type
inevitably leads to the type becoming a record, only record types (or private
types which are implemented as records) can be tagged.
Tagged types provide the mechanism by which types can be extended in-
crementally. The result is that a programmer can create a hierarchy of related
types. Other parts of the program may now wish to manipulate that hierarchy
for their own purposes without being too concerned which member of the hierarchy
it is processing at any one time. Ada is a strongly-typed language and,
therefore, a mechanism is needed by which an object from any member of the
hierarchy can be passed as a parameter.
Class-wide programming is the technique which enables programs to be written
which manipulate families of types. Associated with each tagged type, T,
there is a type T'Class which comprises all the types which are in the family
of types starting at T. If an operation is called with a parameter whose type is
class-wide, then run-time dispatching occurs to the appropriate operation for
the associated actual type.
An object is typically represented by a package containing a tagged type and
its primitive operations.
package Object is
type Obj_Type is tagged limited private;
procedure Op1(O : in out Obj_Type);
procedure Op2(O : in out Obj_Type);
procedure Class_Wide_Op(O : in Obj_Type'Class);
private
type Obj_Type is tagged limited
record
The object can be extended (usually by a child library package - which allows
access to the private part in the parent's declaration).
package Object.Extended is
type Extended_Type is new Obj_Type with private;
procedure Op1(O : in out Extended_Type);
procedure Op2(O : in out Extended_Type);
private
type Extended_Type is new Obj_Type with
record
4.4 Controlled Types
Further support for object-oriented programming is provided by controlled types.
With these types it is possible to define subprograms that are called (automat-
ically) when objects of the type:
ffl are created - initialize;
ffl cease to exist - finalize;
ffl are assigned a new value - adjust.
To gain access to these features, the type must be derived from Controlled,
a predefined type declared in the library package Ada.Finalization, that is, it
must be part of the Controlled class hierarchy. The package Ada.Finalization
defines procedures for Initialize, Finalize and Adjust. When a type is derived
from Controlled these procedure may be overridden. As objects typically
cease to exist when they go out of scope, the exiting of a block may involve a
number of calls of Finalize.
Representing Recoverable Atomic Actions in
Ada 95
In Section 2.1, the requirements for atomic actions were defined. These are now
briefly reviewed to illustrate the general approach to their implementation in
Ada 95.
1. Well-defined boundaries - Each atomic action can be encapsulated in one
or more Ada packages and hence the side boundaries of each action is well-
defined. Subprograms (procedures and functions) in the package interfaces
can be used to provide the start and end points for each task.
2. Indivisibility - Protected objects provide the mechanisms with which the
indivisibility property of an action can be implemented. Protected entries
can be used to provide the required synchronisation on exit of the action.
3. Nesting - Nested actions can be supported by implementing the actions
as abstract data types.
4. Concurrency - Concurrency between the execution of atomic actions is
provided by concurrency between tasks. Groups of tasks which do not
share an action will automatically execute concurrently.
5. Recovery - Backward and forward recovery is programmed using a combination
of protected objects, ATC and exceptions.
5.1 Simple Actions
To illustrate how atomic action can be programmed in Ada, consider first a
simple non-nested action with no recovery between, say, three tasks. The action
is encapsulated in a package with three visible procedure, each of which is called
by the appropriate task. It is assumed that no tasks are aborted and that there
are no deserter tasks.
package Simple_Action is
procedure T1(Params : Param); - called by Task 1
procedure T2(Params : Param); - called by Task 2
procedure T3(Params : Param); - called by Task 3
The body of the package automatically provides the well-defined boundary,
so all that is required is to provide the indivisibility. A protected object,
Action Controller, can be used for this purpose (it provides a similar function
to the Coordinated Atomic Action manager introduced by Randell et al.
(1995)(Xu et al., 1995)). The package's visible procedures call the appropriate
entries in the protected object. Four entries are sufficient. Each component
of the action calls its own individual entry to indicate that it has arrived. On
finishing its component, it calls the Finished entry.
package body Simple_Action is
protected Action_Controller is
entry First; - called by
entry Second; - called by T2
entry Third; - called by T3
entry Finished; - called by all tasks
private
First_Here
Third_Here
Release
- definition of local protected objects for
controlling resources
protected body Action_Controller is separate;
procedure T1(Params) is
begin
acquire resources
- the action itself, communicates with tasks executing
- and T3 via resources
release resources
- similar for second and third task
begin
any initialisation of local resources
The implementation of Action Controller is given below.
protected body Action_Controller is
entry First when not First_Here is
begin
First_Here := True;
First;
entry Second when not Second_Here is
begin
Second_Here := True;
entry Third when not Third_Here is
begin
Third_Here := True;
entry Finished when Release or Finished'Count = 3 is
begin
Release := False;
First_Here := False;
Second_Here := False;
Third_Here := False;
else
Release := True;
The barriers of the entries First, Second and Third ensures that only three
tasks can be active in the action at any one time. Only when all three tasks
have called the Finished entry is the barrier lowered and all tasks released.
The boolean Release is used to program the required release conditions on
Finished. The first two calls on Finished will be blocked as both parts of the
barrier expression are false. When the third call comes, the Count attribute will
become three; the barrier comes down and one task will execute the entry body.
The Release variable ensures that the other two tasks are both released. The
last task to exit must ensure that the barrier is raised again.
Note that Ada's task identifiers can be used if it is necessary to validate the
identity of each task performing the action components.
In the following sections, it will be assumed that only those tasks participating
in the action use the package implementing the action and that each task
will only call its associated operation (and no other).
5.2 Backward Error Recovery
In this section, the Ada ATC facility and exception handling is used to implement
backward error recovery. Any scheme based on backward error recovery
requires the use of some form of recovery cache. This section assumes the existence
of the following generic package for saving and restoring a task's variables.
It is assumed that the strong typing provided by Ada will ensure that the Ada
program itself remains viable.
type Data is private;
package Recovery_Cache is
procedure
procedure
Consider now three Ada tasks which wish to enter into a recoverable atomic
action. Each will call their appropriate procedure in the package given below.
package Conversation is
procedure T1(Params : Param); - called by task 1
procedure T2(Params : Param); - called by task 2
procedure T3(Params : Param); - called by task 3
The body of the package encapsulates the action and ensures that only communication
between the three tasks is allowed. The Controller protected object is
responsible for propagating any error condition noticed in one task to all tasks,
saving and restoring any persistent data in the recovery cache, and ensuring that
all tasks leave the action at the same time. It contains two protected entries
and a protected procedure.
ffl The Wait Abort entry represents the asynchronous event on which the
tasks will wait whilst performing their part of the action (the first call,
indicates a new action and that the Controller should save any persistent
data in the recover cache - the requeue facility is used to place the first
task back on the queue).
ffl Each task calls Done when it has finished. Only when all three tasks have
called Done will they be allowed to leave.
ffl If any task recognises an error condition (either because of a raised exception
or the failure of the acceptance test), it will call Signal Abort. This
will restore any persistent data (defined within the package body) and set
the flag Killed to true, indicating that the tasks must be recovered.
Note, that as backward error recovery will be performed, the tasks are not
concerned with the actual cause of the error. When Killed becomes true, all
tasks in the action receive the asynchronous event. Once the event has been
handled, all task must again wait on the Done entry so that all can terminate
the conversation module together.
with Recovery_Cache;
package body Conversation is
Primary_Failure, Secondary_Failure,
Tertiary_Failure: exception;
type Module is (Primary, Secondary,
package Persistent_Cache is new Recovery_Cache(.);
- for any persistent data to be retained
- between conversations
protected Controller is
entry Wait_Abort;
entry Done;
procedure Signal_Abort;
private
Killed
Releasing
Informed
New_Conversation
- any local protected objects for communication between actions
protected body Controller is
entry Wait_Abort when Killed or New_Conversation is
begin
if New_Conversation then
- save any persistent data in the recovery cache
requeue Wait_Abort with abort;
no return to here
only executed when Killed
Informed
Killed := False;
Informed := 0;
procedure Signal_Abort is
begin
- restore any persistent data
Killed := True;
entry Done when Done'Count = 3 or Releasing is
begin
if Done'Count > 0 then
Releasing := True;
else
Releasing := False;
New_Conversation := True;
procedure T1(Params : Param) is separate;
procedure T2(Params : Param) is separate;
procedure T3(Params : Param) is separate;
The code for each task is contained within a single procedure: T1, etc. Within
each of these procedure, three attempts are made to perform the action. If all
attempts fail, the exception Atomic Action Failure is raised. Each attempt is
surrounded by a call that saves the state and restores the state (if the attempt
fails). Each attempt is encapsulated in a separate local procedure (T1 Primary
etc), which contains a single 'select and then abort' statement to perform the
required protocol with the controller. The recovery cache is used by each task
to save its local data.
procedure T1(Params : Param) is
procedure T1_Primary is
begin
select
triggering event
for all to finish
raise Primary_Failure;
then abort
begin
- code to implement atomic action,
- the acceptance test might raise an exception
Failed then
Controller.Signal_Abort;
else
Controller.Done; -signal completion
exception
when others =>
Controller.Signal_Abort;
procedure T1_Secondary is . ;
procedure T1_Tertiary is . ;
package My_Cache is new Recovery_Cache(.); - for local data
begin
for Try in Module loop
begin
case Try is
when Primary => T1_Primary; exit;
when Secondary => T1_Secondary; exit;
when Tertiary => T1_Tertiary;
exception
when Primary_Failure =>
when Secondary_Failure =>
when Tertiary_Failure =>
raise Atomic_Action_Failure;
when others =>
raise Atomic_Action_Failure;
- similarly for T2 and T3
5.3 Forward Error Recovery
Ada's ATC facility can be used with exceptions to implement atomic actions
with forward error recovery between concurrently executing tasks. Consider,
again, the following package for implementing an atomic action between three
tasks.
package Action is
procedure T1(Params : Param); - called by task 1
procedure T2(Params : Param); - called by task 2
procedure T3(Params : Param); - called by task 3
The body of the package encapsulates the action and ensures that only communication
between the three tasks is allowed. The Controller protected object
is responsible for propagating any exception raised in one task to all tasks, and
for ensuring that all leave the action at the same time.
with Ada.Exceptions;
use Ada.Exceptions;
package body Action is
type Vote_T is (Commit, Aborted);
protected Controller is
entry Wait_Abort(E: out Exception_Id);
entry Done (Vote: Vote_T;
procedure Signal_Abort(E: Exception_Id);
private
entry Wait(Vote: Vote_T;
Killed
Releasing
Informed
- any local protected objects for communication between actions
protected body Controller is
entry Wait_Abort(E: out Exception_Id) when Killed is
begin
Informed
Killed := False;
Informed := 0;
entry Done (Vote: Vote_T; Result : out Vote_T) when True is
begin
Aborted then
Final_Result := Aborted;
requeue Wait with abort;
procedure Signal_Abort(E: Exception_Id) is
begin
Killed := True;
Reason := E;
entry Wait (Vote: Vote_T; Result :out Vote_T)
when or Releasing is
begin
if Wait'Count > 0 then
Releasing := True;
else
Releasing := False;
Final_Result := Commit;
Reason := Null_Occurrence;
procedure T1(Params : Param) is
begin
select
triggering event
exception
then abort
begin
- code to implement atomic action
Controller.Done(Commit, Decision); -signal completion
exception
when E: others =>
exception
- if any exception is raised during the action
- all tasks must participate in the recovery
when E: others =>
- Exception_Identity(E) has been raised in all tasks
- handle exception
if Handled_Ok then
Controller.Done(Commit, Decision);
else
Controller.Done(Aborted, Decision);
Aborted then
raise Atomic_Action_Failure;
procedure T2(Params : Param) is .;
procedure T3(Params : Param) is .;
Each component of the action (T1, T2, and T3) have identical structure. The
component executes a select statement with an abortable part. The triggering
event is signaled by the Controller protected object if any component indicate
that an exception has been raised and not handled locally in one of the compon-
ents. The abortable part contain the actual code of the component. If this code
executes without incident, the Controller is informed that this component is
ready to commit the action. If any exceptions are raised during the abortable
part, the Controller is informed and the identifier of the exception passed.
Note that, unlike backward error recovery (given in the previous section), here
the cause of the error must be communicated.
Once the Controller has received notification of an unhandled exception,
it releases all tasks waiting on the Wait Abort triggering event (any task late
in arriving will receive the event immediately it tries to enter into its select
statement). The tasks have their abortable parts aborted (if started), and the
exception is raised in each task by the statement after the entry call to the
controller. If the exception is successfully handled by the component, the task
indicates that it is prepared to commit the action. If not, then it indicates
that the action must be aborted. If any task indicates that the action is to be
aborted, then all tasks will raise the exception Atomic Action Failure.
The above example illustrates that it is possible to program atomic actions
with forward error recovery in Ada. However, only the first exception to be
passed to the Controller will be raised in all tasks. It is not possible to get
concurrent raising of exceptions, as any further exception raised in an abortable
part is lost when it is aborted.
5.4 Nested Actions
Implementing nested action in Ada 95 requires extensions to the above al-
gorithms. The first is to convert the basic approach so that each atomic action
is a type and, therefore, more than one instance can be created. This can easily
be achieved by introducing the notion of an action identifier. For example,
consider the implementation of actions with forward error recovery given in the
previous section. The package specification now becomes:
package Action is
type Action_Id is private;
function New_Action return Action_Id;
procedure
procedure
procedure
private
type Action_T;
type Action_Id is access Action_T;
As Action Id is private, assignment and comparison are available. The implementation
of the Action T type is a record containing an instance of the
Controller (which becomes a protected type) and instances of any persistent
data and their controlling access protocols:
type Action_T is
record
The interface procedures use the Action Id to generate the call to the correct
controller. For example:
A.C.Wait_Abort;
Once action types have been introduced then nested action can be called within
the body of an action. This will produce the following Ada structure (where
Nested Action is a package containing the action Nested Action Id:
with Nested_Action;
package body Action
procedure T1(A: Action_Id, Other_Params : Param) is
begin
select
then abort
begin
- code to implement atomic action
including
nested action call
A.C.Done(Commit, Decision); -completion of outer action
exception
when E: others =>
exception
when E: others =>
- Exception_Identity(E) has been raised in all tasks
- handle exception
if Handled_Ok then
A.C.Done(Commit, Decision);
else
A.C.Done(Aborted, Decision);
Aborted then
raise Atomic_Action_Failure;
If this code in expanded (to remove the explicit procedure Nested Action.T1),
then the following equivalent structure is obtained:
select
then abort
begin
- code to implement atomic action
including
select
then abort
begin
- code to implement inner atomic action
N.C.Done(Commit, Decision); -signal completion
exception
when E: others =>
A.C.Done(Commit, Decision); -signal completion
exception
when E: others =>
When the outer action has an exception signalled, the outer 'then abort' sequence
of code is aborted. This in turn will cause the inner action to be aborted
without any recovery action. To facilitate recovery in the inner action, it is
necessary to use another Ada 95 facility called Controllled types. Objects of
a controlled type can have (amongst other things) finalisation routines defined.
Hence, each action procedure has the following extra components:
Nested_Action_Id) is new
Finalization.Limited_Controlled with null record;
procedure Finalize(Ar : in out Abort_Recovery);
Where, the finalisation action is to signal to the action controller that the action
is to be aborted:
procedure Finalize(Ar : in out Abort_Recovery) is
begin
action
Ar.N.C.Done(Aborted,
procedure T1(N: Nested_Action_Id) is - in Nested_action
begin
When the inner action (represented by Nested T1) is aborted, the Ar controlled
variables goes out of scope. However, before this can happen, the finalisation
procedure is called. Note that some small modification to the controller tasks
are required as the Finalize routine is called every time the variable goes out
of scope irrespective of whether the action was aborted or not!
5.5 The Deserter Problem and Task Abortion
far it has been assumed that all expected tasks arrive and leave the action.
If a task fails to arrive, all tasks will be blocked trying to leave. To solve this
deserter problem, it is necessary to know how many tasks have entered the
action. When all the tasks that have entered are ready to leave, the action can
complete. A simple modification to the action controller protected object allows
for this:
protected type Controller is
entry Wait_Abort(E: out Exception_Id);
entry Done (Vote: Vote_T;
procedure Signal_Abort(E: Exception_Id);
private
entry Entered(E: out Exception_Id);
entry Wait(Vote: Vote_T;
Killed
Releasing
Informed
Active
A new private entry is provided called Entered and a count, Active, of the
number of tasks active in the action. When a task calls the Wait Abort entry
as part of the ATC statement, the call is immediately accepted. The count
incremented, and the call requeued on the Entered entry.
entry Wait_Abort((E: out Exception_Id) when True is
begin
Active
requeue Entered with abort;
entry Entered(E: out Exception_Id) when Killed is
begin
Informed
Active then
Killed := False;
Informed := 0;
The guard on the Wait entry is now simply changed to include Active and the
count reset to zero when all tasks have finally finished the action:
entry Wait (Vote: Vote_T; Result :out Vote_T)
when Active or Releasing is
begin
if Wait'Count > 0 then
Releasing := True;
else
Releasing := False;
Final_Result := Commit;
Reason := Null_Occurrence;
Active := 0;
All that is now required is for the Action itself to contain timeouts on any
synchronous communication which might block if a cooperating task is absent.
If the communication was essential to the action, then an exception can be
raised if the timeout expires.
One final problem to address is what happens if a task executing an atomic
actions is aborted by another task outside the action. This is similar to the
nested action situation, and the recovery approach is the same.
5.6 Object-Oriented Programming and Reusability
The action systems developed so far can easily be rewritten to make them ex-
tensible. For example, consider a basic package which provides only the action
controller supporting forward error recovery. If the Action Controller protected
type is placed in the private part of the package, the child packages can be
written which will implement atomic actions for particular systems. The code
is, therefore, reused.
with Ada.Exceptions; use Ada.Exceptions;
package Atomic_Action_Support is
abstract tagged limited private;
private
type Vote_T is (Commit, Aborted);
protected type Action_Controller(At_Least : Positive) is
entry Wait_Abort(E: out Exception_Id);
entry Done (Vote: Vote_T;
procedure Signal_Abort(E: Exception_Id);
private
entry Entered(E: out Exception_Id);
entry Wait(Vote: Vote_T;
Killed
Releasing
Active
Action_T(At_Least : Positive) is tagged limited
record
Notice in this example, that the Action T is now a parameterised tagged type.
This allows the type to be extended by the child packages. The parameter
indicates to the controller the minimum number of tasks that must be active in
the action for the action to terminate.
The body of the package simply contains the body of the Action Controller.
This is the same as the one given in Section 5.5 except that the barrier on the
Wait entry is now:
entry Wait (Vote: Vote_T; Result :out Vote_T) when
Active and Active >= At_Least) or Releasing is
Actions can now be created by extending the Action T type. An example of
this is given in the next Section.
6 An Example Action System
Consider an example of controlling the position of a three axis robot. The
software consists of several tasks including a coordinate controller and three
tasks controlling the robot itself (one for each axis). The coordinate controller
task informs the other three tasks when a new position is required. The act of
moving from one position to another is an atomic action; the rest of the system
should only see the robot in one position or another.
The atomic action support for the robot is simply a child package of the
Atomic Action Support given in the previous section. The Action T is extended
to include a protected type used to communicate and synchronise between
the four tasks. Only when the coordinate controller task has written a new
position can the other tasks acquire it. The routines are added for each task.
package Atomic_Action_Support.Robot is
type My_Action_T is new Action_T with private;
subtype Coordinate is Integer range 0 . 180;
type Coordinates is record
procedure
procedure
procedure
procedure
private
protected type Shared_Coord is
procedure Write_Coords(New_Pos: Coordinates);
private
type My_Action_T is new Action_T with
record
Note, here Ada 95's access parameters are used. This avoids having to allocate
the action's data dynamically, and allows run-time dispatching of operations to
be used should the action be further extended.
The body of the package is given below. The structure of each interface
procedure is similar to that given in previous sections
package body Atomic_Action_Support.Robot is
protected body Shared_Coord is separate;
procedure access My_Action_T) is
begin
select
then abort
begin
- code to implement atomic action
including
- move to new position
A.C.Done(Commit, Decision); -signal completion
exception
when E: others =>
exception
when E: others =>
move back to the origin
A.C.Done(Aborted, Decision);
- similarly for Y_Coord and Z_Coord
procedure
begin
select
then abort
begin
- code to implement atomic action
including
A.C.Done(Commit, Decision); -signal completion
exception
when E: others =>
exception
when E: others =>
A.C.Done(Aborted, Decision);
raise Atomic_Action_Failure;
The body of the Shared Coord protected object is
protected body Shared_Coord is
procedure Write_Coords(New_Pos: Coordinates) is
begin
Next_Pos := New_Pos;
New_Value := True;
when New_Value is
begin
X_Got := True;
if Y_Got and Z_Got then
X_Got := False;
Y_Got := False;
Z_Got := False;
New_Value := False;
- similarly for Read_Y and Read_Z
Finally the code for the four tasks can be given:
with Atomic_Action_Support.Robot;
use Atomic_Action_Support.Robot;
procedure Main is
Robot_Action : aliased My_Action_T(At_Least => 4);
- the action, all four tasks must enter before any can leave
type Dimension is (X, Y, Z);
task Control;
the three Axis controller tasks
task body Control is
begin
Coord_Controller(Robot_Action'Access, Start);
loop
- determine next position
Coord_Controller(Robot_Action'Access, Next);
loop
task body Axis is
begin
loop
is
when X => X_Coord(Robot_Action'Access);
when Y => Y_Coord(Robot_Action'Access);
when Z => Z_Coord(Robot_Action'Access);
- perform any required operation at the new position
begin
7 Distributed Systems
Ada defines a distributed system as an "interconnection of one or more processing
nodes (a system resource that has both computational and storage cap-
abilities), and zero or more storage nodes (a system resource that has only
storage capabilities, with the storage addressable by more than one processing
nodes)".
The Ada model for programming distributed systems specifies a partition as
the unit of distribution. Partitions are not first-class language entities (in the
sense that they cannot be declared as types and instances created). Instead, they
comprise aggregations of library units (separately compiled library packages or
subprograms) that collectively may execute in a distributed target execution
environment. It is this inability to declare partition types which is the main
limitation of the Ada model; for example, arrays of partitions are not expressible
within the language(Burns and Wellings, 1995).
Each partition resides at a single execution site where all its library units
occupy the same logical address space. More than one partition may, however,
reside on the same execution site.
Partitions may be either active or passive. The library units comprising
an active partition reside and execute upon the same processing element. In
contrast, library units comprising a passive partition reside at a storage element
that is directly accessible to the nodes of different active partitions that
reference them. This model ensures that active partitions cannot directly access
variables in other active partitions. Variables can only be shared directly
between active partitions by encapsulating them in a passive partition. Communication
between active partitions is defined in the language to be via remote
subprogram calls (however, an implementation may provide other communication
mechanisms).
To help construct distributed programs, Ada distinguishes between different
categories of library units, and imposes restrictions on these categories to maintain
type consistency across the distributed program. Two of the categories are
designated by the following pragmas.
ffl Pure
Pure packages are packages with restrictions which enable them to be
freely replicated in different active or passive partitions without introducing
any type inconsistencies. These restrictions concern the declaration
of objects and types; in particular, variables and named access types are
not allowed unless they are within a subprogram, generic subprogram,
task unit or protected unit.
A type declared in a pure package is considered to be a single declaration,
irrespective of how many times the package is replicated in the distributed
system (the package is replicated once for each partition that references it).
Hence, pure packages enable types to be declared which can subsequently
be used in the communication between partitions.
ffl Remote Call Interface
A Remote Call Interface package defines the interface between active par-
titions. Its body exists only within a single partition. All other occurrences
will have stubs allocated using standard RPC implementation
techniques(Birrell and Nelson, 1984).
The specification of a Remote Call Interface has various restrictions which,
for example, prohibit the definition of a variable (to ensure no remote data
access).
A package which is not categorised is called a normal library package. If it
is included in more than one partition, then it is replicated and all types and
objects are viewed as distinct.
7.1 Programming Distributed Actions
There are many ways in which distributed programs can be constructed in Ada
(see Burns and Wellings (1995)). Here a client-server model is used. The robot
arm problem given in the previous section is used as an example. The following
assumptions are made:
ffl the Control and the Axis tasks are to be distributed
all machines are fail silent(Schlichting and Schneider, 1983)
ffl replication for availability, if required, is achieved transparently to the
program
ffl all communication is reliable.
In common with most distributed programming languages, it is not possible to
take an arbitrary Ada program and distribute it. Therefore, it is necessary to
restructure the one given in Section 6. Figure 2 illustrates the logical partitioning
of the application. Each box represents a partition and each arrow shows
the control flow.
There are several restrictions that are imposed on the interface between
partitions in a distributed Ada system that have particular influence on implementing
distributed actions. They are:
ffl A protected objected cannot be called directly from a remote partition -
instead it is necessary to provide an RPC interface.
ffl Exception identifiers cannot be passed from one partition to another -
consequently, it is necessary to pass an enumeration type instead and
convert between them.
There is no distributed Ada run-time support system. Instead there is a collection
of communicating run-time support systems.
The proposed solution is now considered. First, a pure package is constructed
to provide the types that will be used throughout the system.
Coordinator
Shared Data
Local Action Local Action
Coordinator Coordinator
Coordinator
Subaction
Axes Controller and
Z Axis Task and
Subaction
Axis Task and
Subaction
Y Axis Task and
Subaction
Coordinator
Distributed Action
Coordinator

Figure

2: The partioning of the robot arm problem
package Common_Types is
pragma Pure;
subtype Coordinate is Integer range 0 . 180;
type Coordinates is record
type Vote_T is (Commit, Aborted);
type
type Dimension is (X, Y, Z);
Next it is necessary to construct the servers which will act as the controllers
of the shared data and the distributed action. In Section 6, the data to be
communicated in the action (the new X, Y and Z coordinates) was encapsulated
in a protected object. As this cannot be directly called across a distributed
system, it is necessary to encapsulate it in a package and provide an RPC
interface.
with Common_Types; use Common_Types;
package Shared_Data is
pragma Remote_Call_Interface;
procedure Write_Coords(New_Pos: Coordinates);
procedure Read(D:Dimension;C : out Coordinate);
The package body of this simply declares a protected object identical to the one
given in Section 6. The procedures Write Coords and Read call the appropriate
procedures and entries.
The distributed action controller has a similar translation. It was originally
a centralised controller implemented by a protected type. Again, this is encapsulated
in its own package and given a remote call interface. The exception
identifiers are replaced by the Error Id.
with Common_Types; use Common_Types;
package Global_Action_Support is
pragma Remote_Call_Interface;
procedure Abort_Action_A(E: out
procedure Done_A(Vote: Vote_T;
procedure Signal_Abort_A(E:
procedure Done_Cleanup_A(Vote: Vote_T;
However, there is one further change that needs to be made. This is because
the original controller was called from an asynchronous select (ATC) statement.
Not only is it not possible to call the protected object across the network,
but the ATC statements triggering event must be an entry call (or a delay).
Hence it is not possible to replace the calling code with a simple RPC. To help
solve this problem, it is necessary to distinguish between those tasks waiting
for the action to finish normally and those waiting for it to finish following an
exception (previously the entry Done was used for both occasions). The new
routine Done Cleanup A is used to indicate the latter with Done A being used
for the former.
The body of the package is:
package body Global_Action_Support is
protected type Global_Action_Controller(At_Least : Integer) is
entry Wait_Abort(E: out
entry Done (Vote: Vote_T;
procedure Signal_Abort(E:
entry Done_Cleanup (Vote: Vote_T;
private
entry Entered(E: out
entry Wait(Vote: Vote_T;
entry Wait_Cleanup (Vote: Vote_T;
Killed
Releasing
Informed
Active
Global_Action_Controller(At_Least => 4);
protected body Global_Action_Controller is
entry Wait_Abort(E: out Error_Id) .
- as before
entry Entered(E: out Error_Id) .
- as before
entry Done (Vote: Vote_T; Result : out Vote_T) .
- as before
entry Done_Cleanup (Vote: Vote_T; Result : out Vote_T) when True is
begin
Aborted then
Final_Result := Aborted;
requeue Wait_Cleanup with abort;
procedure Signal_Abort(E:
begin
- it is now possible to get multiple exceptions
- raised, the approach here is to raise the first
- error in all participants
Killed := True;
Reason := E;
First_E := Reason;
entry Wait (Vote: Vote_T; Result :out Vote_T) when
Active and Active >= At_Least) or Releasing is
begin
if Wait'Count > 0 then
Releasing := True;
else
Releasing := False;
Final_Result := Commit;
Reason := Null_Id;
Active := 0;
Killed := True; - flush remote calls
entry Wait_Cleanup when
Active and Active >= At_Least) or Releasing2 is
begin
if Wait_Cleanup'Count > 0 then
else
Active := 0;
Releasing := True; - flush any orphans
procedure Wait_Abort_A(E: out Error_Id) is
begin
- similar for Done_A, Signal_Abort_A and Done_Cleanup_A
It is essential to note that the tasks which call the Global Action Controller
are tasks created by the local implementation of the remote procedure calls.
Therefore, it cannot be guaranteed that a remote task which is waiting on
an ATC event and an entry for the distributed action to complete will have
one call immediately cancelled if the other is accepted. Consequently, it is
necessary for the global controller to flush the tasks waiting. Also note, multiple
errors/exceptions are now possible. Here, the first error is the one returned to
all participants. However, it would be possible to implement exception trees.
Next, the local action controllers are considered. For each ATC it is necessary
to have a local task which forwards the trigger event and waits for it to occur.
with Common_Types; use Common_Types;
package Local_Action_Support is
task type Local_Action_Controller is
entry Wait_Abort(E: out
private
entry Abort_Taken(E: out
The body of this is
with Global_Action_Support;
package body Local_Action_Support is
task body Local_Action_Controller is
begin
loop
accept Wait_Abort(E: out Error_Id) do
requeue Abort_Taken with abort;
forward the request
if
select
accept Abort_Taken(E: out Error_Id) do
else
The task accepts the triggering event and requeues it. It then passes on the
event to the Global Action Controller. When this RPC returns an error
may have been signalled or the call may have been flushed. If an error has been
returned, a conditional accept is issued (again, it is possible that the client will
have gone away by the time this is executed).
Now, the individual participants in the action can be programmed. The local
action of each axes is considered to be an instantiation of a generic package:
with Common_Types; use Common_Types;
local device parameters for move
package Axes_Action is
procedure Move;
with Common_Types; use Common_Types;
with Local_Action_Support; use Local_Action_Support;
with Global_Action_Support; use Global_Action_Support;
with Ada.Exceptions; use Ada.Exceptions;
with Shared_Data; use Shared_Data;
package body Axes_Action is
procedure Move is
begin
select
local call
convert X to Ex
then abort
begin
- code to implement atomic action
including
Shared_Data.Read(D, C); - remote call
- move to C
Done_A(Commit, Decision); - remote call
exception
when E: others =>
convert X to Ex
exception
when E: others =>
Done_Cleanup_A(Commit, Decision); - remote call
-or
Aborted then
raise Atomic_Action_Failure;
Note this has an identically structure to that given previously in the paper. Here,
however, the triggering event is a call to the local action controller task, and all
other calls are RPC calls to the remote action controller and the shared data
controller. On return from the Signal Abort A remote call, the triggering event
might still not have occurred. It is therefore necessary to raise the exception.
If, instead, the task has called Done A this will not return before the triggering
event occurs. The RPC call will, therefore, be cancelled. If the cancellation
does not occur before the Global Action Controller has finished cleaning up,
the controller views the call as a orphan and automatically flushes it. The code
for the robot controller action is similar in structure.
It is now necessary to create the axes tasks themselves. This is done as
follows:
with Common_Types;
use Common_Types;
package Axes is
with Axes_Action;
with Common_Types; use Common_Types;
package body Axes is
task body Axis is
package My_Local_Action is new Axes_Action(A); - appropriate params
begin
loop
- perform operation
exception
when Atomic_Action_Failure =>
Finally, each partition is defined using its own main procedure. For example,
with Axes; use Axes;
with Common_Types; use Common_Types;
procedure Main is
begin
A configuration program can now be used to group together the library packages
into partitions for allocating to the nodes in the distributed system.
8 Conclusion
Atomic actions are a powerful dynamic structuring technique that allow software
fault-tolerant systems to be implemented. However, it is not clear how
a programming language or operating system should support their application.
programming language or operating system provides direct sup-
port. The Ada 95 programming language does, however, provide a rich set
of mechanisms to aid the programming of concurrent and real-time systems.
This paper has shown how these facilities can be used to implement all aspects
of atomic actions. The lack of a truly distributed run-time support system for
Ada means that it is not possible to call protected objects across partitions or to
pass exception identifiers. This does cause some difficulty in the programming
of distributed actions.
The Ada facilities are impressive because each defines support for particular
ffl encapsulation,
ffl communication and synchronisation,
ffl exceptions,
asynchronous transfer of control,
ffl object-oriented programming
ffl finalisation.
These are the fundamental building blocks which allow resuable atomic actions
to be constructed. The ability to program atomic actions in Ada should lead to
their increase use in the engineering of high integrity applications.



--R

Fault Tolerance Principles and Practice:
Implementing remote procedure calls
Structured multiprogramming
Operating System Principles
Programming atomic actions in ada

Concurrency in Ada
recovery in asynchronous systems
Structuring conversations in oper- ation/procedure oriented programming languages
Exception handling: Issues and a proposed notation

Towards a theory of parallel programming


A program structure for error detection and recovery

Fault tolerance using communicating sequential processes
Atomic actions for fault-tolerance using CSP
Approaches to mechanization of the conversation scheme based on monitors
Process structuring
System structure for software fault tolerance
From recovery blocks to concurrent atomic actions
Reliability issues in computing system design
Backward error recovery via conversations in ada


Design of reliable software in distributed systems using the conversation scheme
Fault tolerance in concurrent object-oriented software through coordinated error recovery
--TR

--CTR
Jrg Kienzle , Alexander Romanovsky, Implementing exceptions in open multithreaded transactions based on Ada 95 exceptions, ACM SIGAda Ada Letters, v.XXI n.3, September 2001
Lus Miguel Pinho , Francisco Vasques, Multi-: an Ada 95 based architecture for fault tolerance support of real-time systems, ACM SIGAda Ada Letters, v.XVIII n.6, p.52-60, Nov./Dec. 1998
Marta Patio-Martnez , Ricardo Jimnez-Peris , Sergio Arvalo, Synchronizing group transaction with rendezvous in a distributed Ada environment, Proceedings of the 1998 ACM symposium on Applied Computing, p.2-9, February 27-March 01, 1998, Atlanta, Georgia, United States
Alexander Romanovsky , Bo Sandn, Except for exception handling , ACM SIGAda Ada Letters, v.XXI n.3, September 2001
M. Patio-Martnez , R. Jimnez-Peris , S. Arvalo, Implementing transactions using Ada exceptions: which features are missing?, ACM SIGAda Ada Letters, v.XXI n.3, September 2001
Alexander Romanovsky , Jrg Kienzle, Action-oriented exception handling in cooperative and competitive concurrent object-oriented systems, Advances in exception handling techniques, Springer-Verlag New York, Inc., New York, NY, 2001
Durgesh Pant , K. C. Joshi, Software fault tolerant computing: needs and prospects, Ubiquity, v.8 n.16, p.1-1, April 24, 2007 - April 30, 2007
exception handling;atomic actions;software fault tolerance;conversations;ada 95;recovery blocks