Integrating object-oriented programming and protected objects in Ada 95.
Integrating concurrent and object-oriented programming has been an active research topic since the late 1980's. There is a now a plethora of methods for achieving this integration. The majority of approaches have taken a sequential object-oriented language and made it concurrent. A few approaches have taken a concurrent language and made it object-oriented. The most important of this latter class is the Ada 95 language, which is an extension to the object-based concurrent programming language Ada 83. Arguably, Ada 95 does not fully integrate its models of concurrency and object-oriented programming. For example, neither tasks nor protected objects are extensible. This article discusses ways in which protected objects can be made more extensible.
Introduction
Arguably, Ada 95 does not fully integrate its models of concurrent and object-oriented
programming (Atkinson and Weller, 1993; Wellings et al., 1996; Burns
and Wellings, 1998). For example, neither tasks nor protected objects are exten-
sible. When Ada 95 was designed, the extensions to Ada 83 for object-oriented
programming were, for the most part, considered separate to extensions to the
concurrency model. Although some consideration was given to abandoning protected
types and instead using Java-like synchronised methods in their place,
there was no public debate of this issue. Similarly, there was no public debate
on the issues associated with allowing protected types or tasks to be extended.
The purpose of this paper is to discuss ways in which the Ada 95 concurrency
model can be better integrated with object-oriented programming. The paper
is structured as follows. Section 2 introduces the main problems associated with
the integration of object-oriented and concurrent programming. Section 3 then
describes the main features of the Ada 95 language that are relevant to this work.
Section 4 argues that Ada 95 does not have a well-integrated object-oriented
concurrency model. To achieve better integration, Section 5 proposes that Ada's
protected type mechanism be made extensible and discusses the main syntactic
and semantic issues. Sections 6 then considers how extensible protected types
integrate with Ada's general model of abstraction and inheritance. Sections
7 and 8 discuss how the proposals address the inheritance anomaly and how
they can be used in conjunction with the current object-oriented mechanisms.
Section 9 presents some extended examples and Section 10 draws conclusions
from this work.
Concurrent Object-Oriented Programming
Integrating concurrent and object-oriented programming has been an active
research topic since the late 1980s. There is now a plethora of methods for
achieving this integration (see (Wyatt et al., 1992) for a review). The majority
of approaches have taken a sequential object-oriented language and made it
concurrent (for example, the various versions of concurrent Eiffel (Meyer, 1993;
Caromel, 1993; Karaorman and Bruno, 1993)). A few approaches have taken a
concurrent language and made it object-oriented. The most important of this
latter class is the Ada 95 language which is an extension to the object-based
concurrent programming language Ada 83. A full discussion of this language
will be given in the next section.
In general, there are two main issues for concurrent object-oriented programming

ffl the relationship between concurrent activities and objects - here the distinction
is often between the concept of an active object (which by definition
will execute concurrently with other active objects, for example
(Maio et al., 1989; Mitchell and Wellings, 1996; Newman, 1998)) and where
concurrent execution is created by the use of asynchronous method calls
(or early returns from method calls) ((Yonezawa et al., 1986; Yokote and
Tororo, 1987; Corradi and Leonardi, 1990))
ffl the way in which concurrent activities communicate and synchronise (and
yet avoid the so-called inheritance anomaly (Matsuoka and Yonezawa,
see (Mitchell and Wellings, 1996) for a summary of the various
proposals.
Perhaps the most interesting recent development in concurrent object-oriented
programming is Java (Lea, 1997; Oaks and Wong, 1997). Here we have, notion-
ally, a new language which is able to design a concurrency model within an
object-oriented framework without worrying about backward compatibility is-
sues. The Java model integrates concurrency into the object-oriented framework
by the combination of the active object concept and asynchronous method calls.
All descendants of the pre-defined class Thread have the pre-defined methods
run and start. When start is called, a new thread is created, which executes
run. Subclassing Thread and overriding the run method allows an application
to express active objects. (It is also possible to obtain start and run by implementing
the interface Runnable.) Other methods available on the Thread class
allow for a wide range of thread control. Communication and synchronisation is
achieved by allowing any method of any object to be specified as 'synchronised'.
Synchronised methods execute with a mutual exclusion lock associated with the
object. All classes in Java are derived from the Object class that has methods
which implement a simple form of condition synchronisation. A thread can,
therefore, wait for notification of a single event. When used in conjunction with
synchronised methods, the language provides the functionality similar to that
of a simple monitor (Hoare, 1974).
Arguably, Java provides an elegant, although simplistic, model of object-oriented
concurrency.
3 The Ada 95 Programming Language
The Ada 83 language allowed programs to be constructed from several basic
building blocks: packages, subprograms (procedures and functions), and tasks.
Of these, only tasks were considered to be types and integrated with the typing
model of the language. Just as with any other type in Ada, many instances of a
task type can be declared, tasks can be placed in arrays and records, and pointers
to tasks can be declared and created. Ada 83 fully integrated its concurrency
model into the sequential components of the language. Tasks can encapsulate
data objects as well as other tasks. They are built using a consistent underlying
type model.
3.1 Data-Oriented Synchronization: Protected Types
Ada 95 extends the facilities of Ada 83 in areas of the language where weaknesses
were perceived. One of the innovations was the introduction of data-oriented
communication and synchronization through protected types. Instances of a
protected type are called protected objects; they are basically monitors (Hoare,
but avoid the disadvantages associated with the use of low-level condition
variables. Instead, protected types may have guarded entries similar to those
provided by conditional critical regions (Brinch-Hansen, 1972).
protected type in Ada 95 encapsulates some data items, which can only
be accessed through the protected type's operations. It is declared as shown in
the following example:
protected type Shared_Int is
- Public operations
procedure
function Get return
entry Wait_Until_Zero;
private
Encapsulated data
Current
- Private operations might follow here
The operations of this protected type are implemented in a corresponding body:
protected body Shared_Int is
procedure in Integer) is
begin
Current := Value;
function Get return Integer is
begin
return Current;
entry Wait_Until_Zero
when Current = 0 is - Entry barrier (guard)
begin
Instances of this protected type, i.e. protected objects, can be declared just like
any other variable:
protected object named 'X'
Operations on this shared object can be invoked in the following way:
Some_Variable := X.Get;
Calls to the operations of a protected type are so-called protected actions and
guarantee mutually exclusive access to a protected object with the usual semantics
of multiple readers (function calls, which are read-only) or one writer
(procedure and entry calls).
When an entry is called and its barrier is false, the call is queued and the
calling task is blocked until the call has been finally executed. Otherwise, the call
is accepted and executed in a protected action. At the end of each procedure or
entry call, the barriers of all entries are examined. If a barrier has become true,
a possibly queued call is then executed as part of the same protected action,
i.e. without relinquishing the mutual exclusion in between. This servicing of
entry queues is repeated until either there are no more queued calls or all their
barriers are false. The protected action then terminates.
The following example illustrates the use of entries with a simple bounded
buffer, where items can only be taken from the buffer when it is not empty, and
items can be put into it only when it is not full.
protected type Integer_Bounded_Buffer is
entry
entry Get
private
array (1 . 10) of Integer;
First, Last : Natural :=
Nof_Items : Natural := 0;
protected body Integer_Bounded_Buffer is
entry
when Nof_Items < Buffer'Length is
begin
Buffer (Last) := I;
Last := Last mod Buffer'Length
Nof_Items
entry Get
when Nof_Items > 0 is
begin
I := Buffer(First);
First := First mod Buffer'Length
Nof_Items := Nof_Items -
If Get is called when Nof Items is zero, the call is queued. When another task
calls Put, Nof Items will be incremented. When the entry queues are serviced
after the call to Put has finished, the barrier of Get is now true and the queued
call is allowed to proceed, thus unblocking the task that made that call.
A requeue statement of the form
requeue Target_Entry;
allows an entry to put a call, which it has already begun processing, back on
the same or some other entry queue again. A requeue immediately leaves the
current entry, requeues the call and then initiates entry queue servicing. Once
the requeued call has been executed, control is returned to the task that made
the original call. An example of the requeue statement can be found in section
9.2.
Within the operations of a protected type, the attribute E'Count represents
the number of calls in the queue of entry E.
Potentially blocking calls, in particular entry calls, are forbidden within a
protected action. This language rule helps avoid deadlocks due to the nested
monitors problem and also avoids a possible unbounded priority inversion that
might otherwise occur. This means that a procedure of a protected type may call
other procedures or functions of the same or some other protected object, but
not entries. Functions of a protected type may only call other protected functions
of the same protected object to avoid that they circumvent the read-only
restriction. However, they may call both protected functions and procedures of
other protected objects. Entries may call procedures or functions, but not other
entries; they may only requeue to another entry.
3.2 Object-Orientation: Tagged Types
One of the other main extensions to Ada 83 was the introduction of object-oriented
programming facilities. Here the designers of Ada 95 were faced with
a dilemma. Ada 83's facility for encapsulation was the package. Unfortunately,
packages (unlike tasks) were not fully integrated into the typing model: there
were no package types. Rather than introduce a class-like construct into the
language (as had been done by almost all other object-oriented languages), Ada
95 followed the Oberon (Wirth, 1988) approach and achieved object-orientation
by type extension. The designers argued that Ada 83 already had the ability
to derive types from other types and override their operations. Consequently,
object-orientation was achieved via the introduction of "tagged types".
Tagged types in Ada 95 are record types that can be extended. Thus a class
in Ada is represented by the following:
package Objects is
type Class is tagged record
data attributes of the class
- the following are the primitive operations of the type
procedure Method1 (O: in Class; Params: Some_Type);
procedure Method2 (O: in out Class; Params : Some_Type);
Objects of the class can be created and used by:
with Objects; use Objects;
Params: Some_Type;
begin
Contrast this to a call to an object's method in the more typical object-oriented
paradigm where the call is of the form: Object.Method1(Params). The difference
is purely syntactical; both forms have the same expressive power and
denote the same language construct, namely, a call to a primitive operation of
a tagged type or a call of a method of a class, respectively.
Inheritance in Ada 95 is achieved by extending the parent type and overriding
the primitive operations.
with Objects; use Objects;
package Extended_Objects is
type Extended_Class is new Class with
new data attributes
overridden primitive operations
procedure Method1 (O: in Extended_Class; Params: Some_Type);
procedure Method2 (O: in out Extended_Class; Params : Some_Type);
new primitive operation
procedure Method3 (O: in out Extended_Class; Params : Some_Type);
Polymorphism in Ada 95 is achieved by the use of class-wide types or pointers to
class-wide types. It is possible, for example, to declare a pointer to a hierarchy
of tagged types rooted at a place in the tree of type extensions. This pointer
can then reference any object in the type hierarchy. When a primitive method
is called passing the de-referenced pointer, run-time dispatching occurs to the
correct operation:
type Pointer is access Object.Class'Class;
'Class indicates a class-wide type
Ap: Pointer := new .; - some object derived from Object.Class;
dispatches to appropriate method
In Ada 95, dispatching only occurs when the actual parameter of a call to a
primitive operation is of a class-wide type. This contrasts with some other
object-oriented programming languages where dispatching is the default (e.g.
Java). In order to force dispatching in Ada, the parameter must be explicitly
converted to a class-wide type when invoking the primitive operation. This
situation often occurs when one primitive operation of an object wants to dispatch
to some other primitive operation of the same object. This is called
re-dispatching, and can be achieved by converting the operand to a class-wide
type, as shown in the following example:
type T is tagged record .;
procedure P (X: T) is .;
procedure Q (X: T) is
begin
re-dispatch
type new T with record .;
procedure P (X: T1);
Here, procedure Q does a re-dispatch, by explicitly converting the parameter X
to a class-wide type before invoking P. If this conversion had been omitted and
just called P(X), then the call would be statically bound to the procedure P
of T, regardless of what actual parameter was passed to Q.
It should be noted that Ada allows calls to overridden operations to be
statically bound from outside the defining tagged type. For example, although
the Extended Objects package (defined earlier) has extended the Class tagged
type and overridden Method1, it is possible for a client to write the following:
Eo: Extended_Class;
and call the overridden method explicitly. Arguably this has now broken the
Extended Class abstraction, and perhaps should be disallowed. Such explicit
conversions can only be safely done from within the overridden method itself
when it wishes to call its parent method.
3.3 Object-Oriented Programming and Concurrency
Although task types and protected types are fully integrated into the typing
model of Ada 95, it is not possible to create a tagged protected type or a tagged
task type. The designers shied away from this possibility partly because they
felt that fully integrating object-oriented programming and concurrency was
not a well-understood topic and, therefore, not suitable for an ISO standard
professional programming language. Also, there were inevitable concerns that
the scope of potential language changes being proposed was too large for the
Ada community to accept.
In spite of this, there is some level of integration between tagged types and
tasks and protected objects. Tagged types after all are just part of the typing
mechanism and therefore can be used by protected types and tasks types in the
same way as other types. Indeed paradigms for their use have been developed
(see (Burns and Wellings, 1998) chapter 13). However, these approaches cannot
get around the basic limitation that protected types and task types cannot be
extended.
Making Concurrent Programming in Ada 95
more Object-Oriented
Now that the dust is beginning to settle around the Ada 95 standard, it is
important to begin to look to the future. The object-oriented paradigm has
largely been welcomed by the Ada community. Even the real-time community,
which was originally sceptical of the facilities and worried about the impact
they would have on predictability, is beginning to see some of the advantages.
Furthermore, as people become more proficient in the use of the language they
begin to realise that better integration between the concurrency and object-oriented
features would be beneficial. The goal of this paper is to continue
the debate on how best to achieve full integration in any future version of the
language.
There are the following classes of basic types in Ada:
ffl scalar types - such as integer types, enumeration types, real types, etc.
ffl structured types - such as record types and array types
ffl protected types
ffl task types
ffl access types
Access types are special as they provide the mechanism by which pointers to the
other types can be created. Note that, although access types to subprograms
(procedures and functions) can be created, subprograms are not a basic type of
the language.
In providing tagged types, Ada 95 has provided a mechanism whereby a
structured type can be extended. It should be stressed, though, that only record
types can be extended, not array types. This is understandable as the record
is the primary mechanism for grouping together items which will represent the
heterogeneous attributes of the objects. Furthermore, variable length array
manipulation is already catered for in the language. Similarly, scalar types can
already be extended using subtypes and derived types.
Allowing records to be extended thus is consistent with allowing variable
length arrays, subtypes and derived types.
protected type is similar to a record in that it groups items together.
(In the case of a protected type, these items must be accessed under mutual
exclusion.) It would be consistent, then, to allow a protected type to be extended
with additional items. The following sections will discuss some of the issues in
allowing extensible protected types. The issues associated with extensible task
types are the subject of on-going research.
5 Extensible Protected Types
The requirements for extensible protected types are easy to articulate. In par-
ticular, extensible (tagged) protected types should allow:
ffl new data fields to be added,
ffl new functions, procedures and entries to be added,
ffl functions, procedures and entries to be overridden,
class-wide programming to be performed.
These simple requirements raise many complex semantic issues. Further-
more, any proposed extensions should be fully integrated with the Ada model
of object-oriented programming.
5.1 Declaration and Primitive Operations
For consistency with the usage elsewhere in Ada, the word 'tagged' indicates
that a protected type is extensible. As described in section 3.1, a protected
type encapsulates the operations that can be performed on its protected data.
Consequently, the primitive operations of a tagged protected type are, in effect,
already defined. They are, of course, similar to primitive operations of other
tagged types in spirit but not in syntax, since other primitive operations are
defined by being declared in the same package specification as a tagged type.
Consider the following example:
protected type T is tagged
procedure W (.);
function X (.) return .;
entry Y (.);
private
data attributes of T
W, X, and Y can be viewed as primitive operations on T. Interestingly, the call
O.X takes a syntactic form similar to that in most object-oriented languages.
Indeed, Ada's protected object syntax is in conflict with the language's usual
representation of an 'object' (see Section 3.2).
5.2 Inheritance
Tagged protected types can be extended in the same manner as tagged types.
Hence,
protected type T1 is new T with
procedure W (.); - override T.W
procedure Z (.); - a new method
private
new attributes of
The issue of overriding protected entries will be considered in section 5.4.
One consideration is whether or not private fields in the parent type (T) can
be seen in the child type (T1). In protected types, all data has to be declared as
private so that it can not be changed without first obtaining mutual exclusion.
There are four possible approaches to this visibility issue:
1. Prevent a child protected object from accessing the parent's data. This
would limit the child's power to modify the behaviour of its parent object,
it only being allowed to invoke operations in its parent.
2. Allow a child protected object full access to private data declared in its
parent. This would be more flexible but has the potential to compromise
the parent abstraction.
3. Provide an additional keyword to distinguish between data that is fully
private and data that is private but visible to child types. This keyword
would be used in a similar way to private (much like C++ uses its key-word
'protected' to permit descendent classes direct access to inherited
data items).
4. Allow child protected types to access private components of their parent
protected type if they are declared in a child of the package in which their
parent protected type is declared. This would be slightly inconsistent with
the way protected types currently work in Ada because protected types
do not rely on using packages to provide encapsulation.
The remainder of this paper will assume the second method, as it provides the
most flexibility and requires no new keywords. It is also consistent with normal
tagged types.
If a procedure in a child protected type calls a procedure or function in its
parent, it should not have to wait to obtain the lock on the protected object
before entering the parent, otherwise deadlock would occur. There is one lock
for each instance of a protected type and the same lock should be used when
the protected object is converted to a parent type. This is consistent with the
current Ada approach when one procedure/function calls another in the same
protected object.
5.3 Dispatching and re-dispatching
Given a hierarchy of tagged protected types, it is possible to create class-wide
types and accesses to class-wide types; for example:
type Pt is access protected type T'Class;
P: Pt := new .; - some type in the hierarchy
dispatches to the appropriate projected object.
Of course from within P.W, it should be possible to convert back to the class-wide
type and re-dispatch to another primitive operation. Unfortunately, an operation
inside a tagged protected type does not have the option of converting the
object (on which it was originally dispatched) to a class-wide type because this
object is passed implicitly to the operation. There are two possible strategies
which can be taken:
1. make all calls to other operations from within a tagged protected type
dispatching, or
2. use some form of syntactic change to make it possible to specify whether
to re-dispatch or not.
The first strategy is not ideal because it is often useful to be able to call an
operation in the same type or a parent type without re-dispatching. In addition,
the first strategy is inconsistent with ordinary tagged types where re-dispatching
is not automatic.
A solution according to the second strategy uses calls of the form type.operation,
where type is the type to which the implicit protected object should be con-
verted.   The following is an example of this syntax for a re-dispatch:
protected body T is
procedure P (.) is
begin
T'Class indicates the type to which the protected object (which is in the hierarchy
of type T'Class but which is being viewed as type T) that was passed
implicitly to P should be view converted. This allows it to define which Q procedure
to call. This syntax is also necessary to allow an operation to call an
overridden operation in its parent, for example:
protected body T1 is - an extension of T
procedure W (.) is - overrides the W procedure of T
begin
calls the parent operation
This new syntax does not conflict with any other part of the language because
it is strictly only a type that precedes the period. If it could be an instance of
a protected type then the call could be mis-interpreted as an external call: the
An alternative syntactic representation might be type'operation.
Ada Reference Manual (Intermetrics, 1995) distinguishes between external and
internal calls by the use, or not, of the full protected object name (Burns and
Wellings, 1998). The call would then be a bounded error.
Requeuing can also lead to situations where re-dispatching is desirable. Just
as with procedures, re-dispatching would only occur when explicitly requested,
so for example, in a protected type T, requeue E would not dispatch whereas
requeue T'Class.E would dispatch. Requeuing to a parent entry would require
barrier re-evaluation. Requeues from other protected objects or from accept
statements in tasks could also involve dispatching to the correct operation in a
similar way.
5.4 Entry Calls
Allowing entries to be primitive operations of extensible protected types raises
many inter-related complex issues. These include:
1. Can a child entry call its parent's entry? - From an object-oriented per-
spective, it is essential to allow the child entry to call its parent. This is
how reuse is achieved. Unfortunately, from the protected object perspec-
tive, calling an entry is a potentially suspending operation and these are
not allowed within the body of a protected operation (see section 3.1). It
is clear that a compromise is required and that a child entry must be able
to extend the facilities provided by its parent.
2. What is the relationship, if any, between the parent's barrier and the
child's barrier? - There are three possibilities: no relationship, the child
can weaken the parent's barrier, or the child can strengthen the parent's
barrier. Fr-lund (Fr-lund, 1992) suggests that as the child method extends
the parent's method, the child must have more restrictive synchronisation
constraints, in order to ensure that the parent's state remains consistent
y . However, he also indicates that if the behaviour of the child method
totally redefines that of the parent, it should be possible to redefine the
synchronisation constraints. Alternatively, it can also be argued that the
synchronisation constraints of the child should weaken those of the par-
ent, not strengthen them, in order to avoid violating the substitutability
property of subtypes (Liskov and Wing, 1994).
3. How many queues does an implementation need to maintain for an over-ridden
- If there is no relationship between the parent and the child
barrier, it is necessary to maintain a separate entry queue for each over-ridden
entry. If there is more than one queue, the 'Count attribute should
reflect this. Hence 'Count might give different values when called from
the parent or when called from the child. A problem with using separate
entry queues with different barriers for overridden and overriding entries
is that it is harder to theorise about the order of entries being serviced.
Normally entries are serviced in first-in, first-out (FIFO) order but with
separate queues, each with a separate barrier, this might not be possible.
For example, a later call to an overridden entry will be accepted before an
y It perhaps should be noted that where the child has access to its parent's state, barrier
strengthening is not a sufficient condition to ensure the consistency of that state, as the child
can make the barrier false before calling the entry. See also the discussion in section 5.4.1.
earlier call to an overriding entry if the barrier for the overridden entry
becomes true with the overriding entry's barrier remaining false.
4. What happens if a parent entry requeues to another entry? - When an
entry call requeues to another entry, control is not returned to the calling
entry but to the task which originally made the entry call (see section
3.1). This means that when a child entry calls its parent and the parent
entry requeues, control is not returned to the child. Given that the code
of the parent is invisible to the child, this would effectively prohibit the
child entry from undertaking any post-processing.
In order to reduce the number of options for discussion, it is assumed that
child entries must strengthen their parent's barrier for the remainder of the
paper. The syntax and when is used to indicate this z . To avoid having the
body of a child protected object depend on the body of its parent, it is necessary
to move the declaration of the barrier from the body to the specification of the
protected type (private part). Consider
protected type T is tagged
private
I: Integer := 0;
barrier given in the private part
protected type T1 is new T with
private
entry E and when I > 0;
If a call was made to A.E, this would be statically defined as a call to T1.E and
would be subject to its barrier (E'Count ? 1 and I ? 0). The barrier would
be repeated in the entry body.
Even with barrier strengthening, the issue of barrier evaluation must be
addressed. Consider the case where a tagged protected object is converted to
its parent type (using a view conversion external to the protected type) and
then an entry is called on that type. It is not clear which barrier needs to be
passed. There are three possible strategies that can be taken:
1. Use the barrier associated with the exact entry which is being called,
ignoring any barrier associated with an entry which overrides this exact
entry. As the parent type does not know about new data added in the
child, it could be argued that allowing an entry in the parent to execute
when the child has strengthened the barrier for that entry should be safe.
Unfortunately, this is not the case. Consider a bounded buffer which
has been extended so that the Put and Get operations can be locked.
Here, if the lockable buffer is viewed converted to a normal buffer and
Get/Put called with only the buffer barriers evaluated, a locked buffer
will be accessible even if it is locked. Furthermore, this approach would
z Short circuit control forms such as and then when could also be made available.
also mean that there would be separate entry queues for overridden entries.
The problems associated with maintaining more than one entry queue per
overridden entry have already been mentioned.
2. Use the barrier associated with the entry to which dispatching would occur
if the object was converted to a class wide type (i.e., the barrier of the
entry of the object's actual type). This is the strongest barrier and would
allow safe re-dispatching in the entry body. This method results in one
entry queue per entry instead of one for each entry and every overridden
entry. However, it is perhaps misleading as it is the parent's code which
is executed but the child's barrier expression that is evaluated.
3. Allow view conversions from inside the protected object but require that
all external calls are dispatching calls. Hence, there is only one entry
queue, and all external calls would always invoke the primitive operations
of the object's actual type. The problem with this approach is that currently
Ada does not dispatch by default. Consequently, this approach
would introduce an inconsistency between the way tagged types and extensible
protected types are treated.
For the remainder of this paper, it is assumed that external calls to protected
objects always dispatch x .
5.4.1 Calling the Parent Entry and Parent Requeues
So far this section has discussed the various issues associated with overridden
entry calls. However, details of how the child entry actually calls its parent
have been left unspecified. The main problem is that Ada forbids an entry from
explicitly calling another entry (see section 3.1). There are several approaches
to this problem.
1. Use requeue. - Although Ada forbids nested entry calls, it does allow an
entry call to be requeued. Hence, the child can only requeue to the parent.
Requeue gives the impression of calling the parent but it is not possible for
the child to do any post-processing once the parent entry has executed (as
the call returns to the caller of the child entry). As a requeue, the parent's
barrier would have to be re-evaluated. Given that the child barrier has
strengthened the parent's barrier, the parent's barrier would normally be
open. If this is not the case, an exception is raised (to queue the call would
require more than one entry queue). - Furthermore, if atomicity is to be
maintained and the parent requeue is to be part of the same protected
action, the parent entry must be serviced before any other entries whose
barriers also happen to be open. Hence, this requeue has slightly different
semantics from a requeue between unrelated entries.
2. Allow the child entry to call the parent entry and treat that call as a procedure
call. - It is clear that calling the parent entry is different from a
x To harmonize with regular tagged types a new pragma could be introduced called "Ex-
ternal Calls Always Dispatch" which can be applied to regular tagged types.
- With the requeue approach and multiple entry queues, there need not be any relationship
between the parent and the child barriers. Such an approach has already been ruled out in
the previous subsection.
normal entry call; special syntax has already been introduced to facilitate
it (see section 5.3). In this approach, the parent call is viewed as a procedure
call and therefore not a potentially suspending operation. However,
the parent's barrier is still a potential cause for concern. One option is to
view the barrier as an assertion and raise an exception if it is not true.
k The other option is not to test the barrier at all, based on the premise
that the barrier was true when the child was called and, therefore, need
not be re-evaluated until the whole protected action is completed.
With either of these approaches, there is still the problem that control is
not returned to the child if the parent entry requeues requests to other entries
for servicing. This, of course, could be made illegal and an exception raised.
However, requeue is an essential part of the Ada 95 model and to effectively
forbid its use with extensible protected types would be a severe restriction.
The remainder of this paper will assume a model where parent calls are
treated as procedure calls (the issue of the assertion is left open) and requeue
in the parent is allowed. A consequence of this is that no post-processing is
allowed after a parent call.
6 Integration into the Full Ada 95 Model
The above section has considered the basic extensible protected type model. Of
course, any proposal for the introduction of such a facility must also consider
the full implications of its introduction. This section considers the following
topics:
ffl private types,
abstract types, and
ffl generics and mix-in inheritance
6.1 Private Types
The encapsulation mechanism of Ada 95, the package, gives the programmer
great control over the visibility of the entities declared in a package. In par-
ticular, Ada 95 supports the notion of private and limited private types, i.e.
types whose internal structure is hidden for clients of the packages (where the
types are declared) and that can be modified only through the primitive operations
declared in these packages (for these types). A protected type is a limited
type, hence it is necessary to show how extensible protected types integrate into
limited private types. The following illustrates how this is easily achieved.
In order to make a type private, its full definition is moved to the private
part of the package. This can also be done for extensible protected types:
package Example1 is
protected type Pt0 is tagged private;
private
k Special consideration would need to be given to barriers which use the 'Count attribute
in the parent, since these will clearly change when the child begins execution.
protected type Pt0 is tagged
primitive operations.
private
data items etc.
Note that in this example, the primitive operations of type Pt0 are all declared
in the private part of the package and are thus visible only in child packages of
package Example1. Other packages cannot do anything with type Pt0, because
they do not have access to the type's primitive operations. Nevertheless, this
construct can be useful for class-wide programming using access types, e.g.
through
type Pt_Ref is access Pt0'Class;
Private types can also give a finer control over visibility. One might declare
a type and make some of its primitive operations publicly visible while other
primitive operations would be private (and thus visible only to child packages).
For example:
package Example2 is
protected type Pt1 is tagged
primitive operations, visible anywhere
with private
data items etc.
private
protected type Pt1 is tagged
private primitive operations, visible only in child packages
private
data items etc.
Note that the public declaration of type Pt1 uses "with private" instead of only
"private" to start its private section. This is supposed to give a syntactical indication
that the public view of Pt1 is an incomplete type that must be completed
later on in the private part of the package.
Alternatively a protected type can be declared to have a private extension.
Given a protected type Pt2:
package Base is
protected type Pt2 is tagged
private
A private extension can then be written as:
with Base;
package Example3 is
protected type Pt3 is new Base.Pt2 with private;
private
protected type Pt3 is new Base.Pt2 with
Additional primitive operations
private
Additional data items
Here, only the features inherited from Pt2 are publicly visible, the additional
features introduced in the private part of the package are private and hence
visible only in child packages of package Example3.
Private types can be used in Ada 95 to implement hidden and semi-hidden
inheritance, two forms of implementation inheritance (as opposed to interface in-
heritance, i.e. subtyping). For instance, one may declare a tagged type publicly
as a root type (i.e., not derived from any other type) while privately deriving
it from another tagged type to reuse the latter's implementation. This hidden
inheritance is also possible with extended protected types. Given the above
package Base, hidden inheritance from Pt2 can be implemented as follows:
with Base;
package Example4 is
- the public view of Pt4 is a root type
protected type Pt4 is tagged
primitive operations, visible anywhere
with private
data items etc.
private
- the private view of Pt4 is derived from Pt2
protected type Pt4 is new Base.Pt2 with
additional primitive operations, visible only in child packages
with private
additional data items etc.
The derivation of Pt4 from Pt2 is not publicly visible: operations and data
items inherited from Pt2 cannot be accessed by other packages. If some of
the primitive operations inherited from Pt2 should in fact be visible in the
public view of Pt4, too, Pt4 must re-declare them and implement them as
call-throughs to the privately inherited primitive operations of Pt2. In child
packages of package Example4, the derivation relationship is exposed and hence
these inherited features are accessible in child packages.
Semi-hidden inheritance is similar in spirit, but exposes part of the inheritance
relation. Given an existing hierarchy of extensible protected types:
package Example5_Base is
protected type Pt5 is tagged
private
protected type Pt6 is new Pt5 with
private
One can now declare a new type Pt7 that uses interface inheritance from Pt5,
but implementation inheritance from some type derived from Pt5, e.g. from
with Example5_Base; use Example5_Base;
package Example5 is
protected type Pt7 is new Pt5 with
with private
private
protected type Pt7 is new Pt6 with
private
As these examples show, extensible protected types offer the same expressive
power concerning private types as ordinary tagged types. In fact, because protected
types are an encapsulation unit in their own right (in addition to the
encapsulation provided by packages), extensible protected types offer an even
greater visibility control than ordinary tagged types. Primitive operations of
an extensible protected type declared in the type's private section are visible
only within that type itself or within a child extension of that type. Combining
this kind of visibility (which is similar to Java's `protected' declarator) with the
visibility rules for packages gives some visibility specifications that do not exist
for ordinary tagged types.
There is one difficulty with this scheme, though. It is currently possible in
Ada 95 to define a limited private type that is implemented as a protected type.
This raises the question whether the following should be legal:
package Example6 is
type T is tagged limited private;
private
protected type T is tagged
private
Here, although child packages could treat T as an extensible protected type,
other client packages could do very little with the type. Furthermore, the mixture
of protected and non-protected views of one and the same type may give
rise to incalculable implementation problems because in some cases accesses to
an object would have to be done under mutual exclusion even if the view of
the object's type was not protected, simply because its full view was a protected
type. Consequently, the kind of private completion shown in Example6
is probably best disallowed.
6.2

Abstract

Extensible Protected Types
Ada 95 allows tagged types and their primitive operations to be abstract. This
means that instances of the type cannot be created. An abstract type can be an
extension of another abstract type. A concrete tagged type can be an extension
from an abstract type. An abstract primitive operation can only be declared for
an abstract type. However, an abstract type can have non-abstract primitive
operations.
The Ada 95 model can easily be applied to extensible protected types. The
following examples illustrate the integration:
protected type Ept is abstract tagged
- Concrete operations:
function F (.) return .;
procedure P (.);
Abstract operations:
function F1 (.) return . is abstract;
procedure P1 (.) is abstract;
entry E1 (.) is abstract;
private
The one issue that is perhaps not obvious concerns whether an abstract entry
can have a barrier. On the one hand, an abstract entry cannot be called so any
barrier is superfluous. On the other hand, the programmer may want to define
an abstraction where it is appropriate to guard an abstract entry. For example:
protected type Lockable_Operation is abstract tagged
procedure Lock;
procedure Unlock;
entry Operation (.) is abstract;
private
entry Operation (.) when not Locked;
The bodies of Lock and Unlock set the Locked variable to the corresponding
values. Now because of the barrier strengthening rule, the when not Locked
barrier will automatically be enforced on any concrete implementation of the
operation.
The remainder of the paper will assume that abstract entries do not have bar-
riers. The above example can be rewritten with a concrete entry for Operation
that has a null body. It should be noted, however, that with a concrete null-
operation, one cannot force concrete children to supply an implementation for
the entry. With an abstract entry, one can.
6.3 Generics and Mix-in Inheritance
Ada 95 does not support multiple inheritance. However, it does support various
approaches which can be used to achieve the desired affect. One such approach
is mix-in inheritance where a generic package which can take a parameter of a
tagged type is declared. A version of Ada with extensible protected types must
also allow them to be parameters to generics and hence take part in mix-in
inheritance.
As with normal tagged types, two kinds of generic formal parameters can be
defined:
type Base_Type is [abstract] protected tagged private;
type Derived_From is [abstract] new protected Derived [with private];
In the former, the generic body has no knowledge of the extensible protected
type actual parameter. In the latter, the actual type must be a type in the tree
of extensible protected types rooted at Derived.
Unfortunately, these facilities are not enough to cope with situations involving
entries. One of the causes of the inheritance anomaly (Matsuoka and
Yonezawa, 1993)(see also section 7) is that adding code in a child object affects
the synchronisation code in the parent. Consider the case of a predefined lock
which can be mixed in with any other protected object to define a lockable
version. Without extra functionality, there is no way to express this. For these
reasons, the generic modifier entry !? is used to mean all the entries of the
actual parameter. The lockable mix-in type can now be achieved:
type Base_Type is [abstract] protected tagged private;
package Lockable_G is
protected type Lockable_Type is new Base_Type with
procedure Lock;
procedure Unlock;
private
entry <> and when not Locked;
Lockable_G;
The code entry !? and when not Locked indicates that all entries in the
parent protected type should have their barriers strengthened by the boolean
expression not Locked.
The entry !? feature makes it possible to modify the barriers of entries that
are unknown at the time the generic unit is written. At the time the generic
unit is instantiated, the entries of the actual generic parameter supplied for
Base Type are known, and entry !? then denotes a well-defined set of primitive
operations.
This generic barrier modifier is similar to Fr-lund's ``all-except'' specifier (Fr-lund,
1992), except that the latter also applies to primitive operations that are added
later on in further derivations, whereas entry !? does not. If new primitive
operations are added in further derivations, it is the programmer's responsibility
to make sure that these new entries get the right barriers (i.e., include when
not Locked).
Clearly, the effect is limited to entries while procedures are unaffected. This
gives rise to the following anomaly: If all the barriers need to be strengthened
by adding the condition not Locked, it may well be that the inherited procedures
need to be similarly guarded. This cannot be done without introducing
a mechanism for overriding procedures with entries. This is an Ada-specific
inheritance anomaly, which is discussed in the next section.
7 Inheritance Anomaly
The combination of the object-oriented paradigm with mechanisms for concurrent
programming may give rise to the so-called "inheritance anomaly" (Matsuoka
and Yonezawa, 1993). An inheritance anomaly exists if the synchronization between
operations of a class is not local but may depend on the whole set of
operations present for the class. When a subclass adds new operations, it may
therefore become necessary to change the synchronization defined in the parent
class to account for these new operations. This section examines how extensible
protected types can deal with this inheritance anomaly.
Synchronization for extensible protected types is done via entry barriers. An
entry barrier can be interpreted in two slightly different ways:
ffl as a precondition (which must become a guard when concurrency is introduced
in an object-oriented programming language, as (Meyer, 1997) ar-
gues). In this sense, entries are the equivalent of partial operations (Herlihy
and Wing, 1994).
ffl as a synchronization constraint.
The use of entry barriers (i.e., guards) for synchronization makes extended protected
types immune against one of the kinds of inheritance anomalies identified
by (Matsuoka and Yonezawa, 1993): guards are not subject to inheritance
anomalies caused by a partitioning of states.
To avoid a major break of encapsulation, it is mandatory for a concurrent
object-oriented programming language to have a way to re-use existing synchronization
code defined for a parent class and to incrementally modify this
inherited synchronization in a child class. In our proposal, this is given by the
and when    clause, which incrementally modifies an inherited entry barrier
and hence the inherited synchronization code.
Inheritance anomalies in Ada 95 with extended protected types can still
occur, though. As (Mitchell and Wellings, 1996) argue, the root cause of inheritance
anomalies lies in a lack of expressive power of concurrent object-oriented
programming languages: if not all five criteria identified by (Bloom, 1979) are
fulfilled, inheritance anomalies may occur. Ada 95 satisfies only three of these
criteria; synchronization based on history information cannot be expressed directly
using entry barriers (local state must instead be used to record execution
history), and synchronization based on request parameter values also is not
possible directly in Ada 95. The example for the resource controller shown in
section 9.2 exhibits both of these inheritance anomalies. Because the barrier
of entry Allocate N cannot depend on the parameter N itself, an internal re-
queue to Wait For N must be used instead. The synchronization constraint for
Wait For N itself is history-sensitive: the operation should be allowed only after
a call to Deallocate has freed some resources. As a result, Deallocate must
be overridden to record this history information in local state, although both
the synchronization constraints for Deallocate itself as well as its functionality
remain unchanged.
In addition to that, extensible protected types may suffer from an Ada-
specific inheritance anomaly. As synchronization is done via barriers, only
entries can be synchronised, but not procedures. If the synchronization constraints
of a subtype should restrict an inherited primitive operation that was
implemented as a procedure in the parent type, the subtype would have to over-ride
this procedure by an entry. However, when using class-wide programming,
a task may assume that a protected operation is implemented as a procedure
(as that is what the base type indicates) and is therefore non-blocking. At
run-time the call might dispatch to an entry and block on the barrier, which
would make the call illegal if it occurred within a protected action. For these
reasons, overriding procedures with entries should not be allowed for extensible
protected types.
As discussed in section 6.3, further Ada-specific inheritance anomalies that
might arise when mix-in inheritance is used can be avoided by providing additional
functionality for generics. The new generic barrier modifier entry !?
alone is not sufficient to avoid the introduction of new Ada-specific inheritance
anomalies. Because the generic mix-in class must define the synchronization for
the complete class resulting from the combination of the mix-in class with some
a priori unknown base class, the entry !? barrier modifier was introduced. It
allows the mix-in class to impose its own synchronization constraints on an
unknown set of inherited operations. However, it is also necessary to have a
way for the mix-in class to adapt the synchronization of its additional primitive
operations to the synchronization constraints imposed by an actual base type.
or and then when
When the generic mix-in is instantiated with some base type to create a new result
type, it must be possible to parametrise the mix-in's synchronization based
upon the base type in order to obtain the correct synchronization for the new
result type. How such a parametrisation could be obtained is still a topic of
on-going research.
8 Interaction with Tagged Types
So far, the discussion has focused on how protected types can be extended.
This section now considers the interaction between tagged types and protected
tagged types.
Consider the following which defines a simple buffer:
package Simple_Buffer is
type Data_T is tagged private;
procedure Write (M : in out
procedure Read (M : in
private
type Data_T is tagged
record
Such a buffer can only be used safely in a sequential environment. To make a
pre-written buffer safe for concurrent access requires it to be encapsulated in a
protected type. The following illustrates how this can easily be achieved.
protected type Buffer is tagged
procedure Write
procedure Read (X : out Integer);
private
The buffer can now only be accessed through its protected interface.
Of course if the Buffer protected type is extended, the following will dispatch
on the buffer.
type B is access Buffer'Class;
new .;
Alternatively, Simple Buffer.Data T can be made protected but not encapsulated
by the following:
protected type Buffer is tagged
procedure Write (M : in out
procedure Read (M : in out
private
This would allow the buffer to be accessed directly (without the protection
overheads) where the situation dictates that it is safe to do so.
Combining extensible protected types with class-wide tagged types allow for
even more powerful paradigms. Consider
protected type Buffer is tagged
procedure Write (M : in out Simple_Buffer.Data_T'Class;
procedure Read (M : in out Single_Data_T'Class;
private
Here, both the protected type and the tagged type can be easily extended.
The program can arrange for dispatching on the Buffer and from within the
Write/Read routines. Further, by using access discriminants the data can be
encapsulated and protected from any concurrent use.
type Ad is access Simple_Buffer.Data_T'Class;
protected type tagged - a normal discriminant
procedure Write
procedure Read (X : out Integer);
private
type B is access Buffer'Class;
new Buffer(new Simple_Buffer.Data_T).;
Here, B1 will dispatch to the correct buffer and Write/Read will dispatch to the
correct data which will be encapsulated.
9 Examples
This section presents two examples illustrating the principles discussed in this
paper. They assume all external calls dispatch, there is no post-processing after
parent calls, no checking of parents' barriers, and that the child has access to
the parent's state.
9.1 Signals
In concurrent programming, signals are often used to inform tasks that events
have occurred. Signals often have different forms: there are transient and persistent
signals, those that wake up only a single task and those that wake up
all tasks. This sections illustrates how these abstractions can be built using
extensible protected types.
Consider first, an abstract definition of a signal.
package Signals is
protected type Signal is abstract
procedure Send;
entry Wait is abstract;
private
Signal_Arrived
type All_Signals is access Signal'Class;
package body Signals is
protected body Signal is abstract
procedure Send is
begin
Signal_Arrived := True;
Now to create a persistent signal:
with Signals; use Signals;
package Persistent_Signals is
protected type Persistent_Signal is new Signal with
entry Wait;
private
entry Wait when Signal_Arrived;
package body Persistent_Signals is
protected body type Persistent_Signal is
entry Wait when Signal_Arrived is
begin
Signal_Arrived := False;
To create a transient signal
with Signals; use Signals;
package Transient_Signals is
protected type Transient_Signal is new Signal with
procedure Send;
entry Wait;
private
entry Wait when Signal_Arrived;
package body Transient_Signals is
protected body type Transient_Signal is
procedure Send is
begin
return;
entry Wait when Signal_Arrived is
begin
Signal_Arrived := False;
To create a signal which will release all tasks.
type Base_Signal is new protected Signal;
package Release_All_Signals is
protected type Release_All_Signal is new Base_Signal with
entry Wait;
private
entry Wait and when True;
package body Release_All_Signals is
protected body Release_All_Signal
entry Wait and when True is
begin
if Wait'Count /= 0 then
return;
Base_Signal.Wait;
Now, of course,
My_Signal : All_Signals := .;
will dispatch to the appropriate signal handler.
9.2 Advanced Resource Control
Resource allocation is a fundamental problem in all aspects of concurrent pro-
gramming. Its consideration exercises all Bloom's criteria (see section 7) and
forms an appropriate basis for assessing the synchronisation mechanisms of concurrent
languages, such as Ada.
Consider the problem of constructing a resource controller that allocates
some resource to a group of client agents. There are a number of instances of
the resource but the number is bounded; contention is possible and must be
catered for in the design of the program. (Mitchell and Wellings, 1996) propose
the following resource controller problem as a benchmark for concurrent object-oriented
programming languages.
Implement a resource controller with 4 operations:
ffl Allocate: to allocate one resource,
ffl Deallocate: to deallocate a resource (which thus becomes
available again for allocation)
ffl Hold: to inhibit allocation until a call to
ffl Resume: which allows allocation again.
There are the following constraints on these operations:
1. Allocate is accepted when resources are available and the controller
is not held (synchronization on local state and history)
2. Deallocate is accepted when resources have been allocated
(synchronization on local state)
3. calls to Hold must be serviced before calls to Allocate (syn-
chronization on type of request)
4. calls to Resume are accepted only when the controller is held
(synchronization on history information).
As Ada 95 has no deontic logic operators, not all history information can be
expressed directly in barriers. However, it is possible to use local state variables
to record execution history.
The following solution simplifies the presentation by modelling the resources
by a counter indicating the number of free resources. Requirement 2 is interpreted
as meaning that an exception can be raised if an attempt is made to
deallocate resources which have not yet been allocated.
package Rsc_Controller is
Max_Resources_Available : constant Natural := 100; - For example
No_Resources_Allocated raised by deallocate
protected type Simple_Resource_Controller is tagged
entry Allocate;
procedure Deallocate;
entry Hold;
entry Resume;
private
entry Allocate when Free > 0 and not Locked and - req. 1
entry Hold when not Locked;
entry Resume when Locked; - req. 4
The body of this package simply keeps track of the resources taken and freed,
and sets and resets the Locked variable.
package body Rsc_Controller is
protected body Simple_Resource_Controller is
entry Allocate when Free > 0 and not Locked and
begin
Free := Free -1; - allocate resource
Taken
procedure Deallocate is
begin
raise No_Resources_Allocated;
Free return resource
Taken := Taken -
entry Hold when not Locked is
begin
Locked := True;
entry Resume when Locked is
begin
Locked := False;
Rsc_Controller
(Mitchell and Wellings, 1996) then extend the problem to consider the impact
of inheritance:
Extend this resource controller to add a method: Allocate N which
takes an integer parameter N and then allocates N resources. The
extension is subject to the following additional requirements:
5. Calls to Allocate N are accepted only when there are at least
available resources.
6. Calls to Deallocate must be serviced before calls to Allocate
or Allocate N.
The additional constraint that calls must be serviced in a FIFO Within Priorities
fashion is ignored here. (Mitchell and Wellings, 1996) also do not implement
this, and in Ada 95, it would be done through pragmas.
Note that this specification is flawed, and the implementation shown in
(Mitchell and Wellings, 1996) also exhibits this flaw: if Deallocate is called
when no resources are allocated, the resource controller will deadlock and not
service any calls to Deallocate, Allocate, or Allocate N. In this implemen-
tation, this has been corrected implicitly, because calling Deallocate when no
resources are allocated is viewed as an error and an exception is raised.
Requirement 5 is implemented by requeueing to Wait For N if not enough
resources are available.
Requirement 6 is implicitly fulfilled because calls to Deallocate are never
queued since Deallocate is implemented as a procedure.
with use Rsc_Controller;
package Advanced_Controller is
protected type Advanced_Resource_Controller is
new Simple_Resource_Controller with
entry Allocate_N (N : in Natural);
procedure Deallocate;
- Ada-specific anomaly: because barriers cannot access parameters,
- we must also override this method so that we can set 'Changed'
- (see below).
private
entry Allocate_N when
Free > 0 and not Locked and - req. 1
- Note: Ada does not allow access to parameters in a barrier (purely
- for efficiency reasons). Such cases must in Ada always be imple-
- mented by using internal suspension of the method through a
statement. Everything below is just necessary overhead
- in Ada 95 to implement the equivalent of having access to
- parameters in barriers.
- Indicates which of the two 'Wait_For_N' entry queues is the one
- that currently shall be used. (Two queues are used: one queue
- is used when trying to satisfy requests, requests that cannot
- be satisfied are requeued to the other. Then, the roles of the
- two queues are swapped. This avoids problems when the calling
tasks have different priorities.)
Changed
something is deallocated. Needed for correct
- implementation of 'Allocate_N' and 'Wait_For_N'. Reset each
outstanding calls to these routines have been serviced.
actually encodes the history information "Wait_For_N"
- is only accepted after a call to 'Deallocate'.
entry Wait_For_N (for Queue in Boolean) (N : in Natural);
entry Wait_For_N (for Queue in Boolean) when
not Locked and Hold'Count = 0 and
This private entry is used by 'Allocate_N' to requeue to if
- less than N resources are currently available.
package body Advanced_Controller
protected body Advanced_Resource_Controller is
procedure Deallocate is
- Overridden to account for new history information encoding
- needed for access to parameter in the barrier of Allocate_N.
begin
Changed := True;
entry Allocate_N (N : in Natural) when
Free > 0 and
not Locked and
begin
if Free >= N then
Free := Free - N;
Taken
else
requeue Wait_For_N(Current_Queue);
entry Wait_For_N (for Queue in Boolean)(N : in Natural) when
not Locked and Hold'Count = 0 and
Changed is
begin
Current_Queue := not Current_Queue;
Changed := False;
if Free >= N then
Free := Free - N;
Taken
else
requeue Wait_For_N(not Queue);
Conclusions
This paper has argued that Ada 95's model of concurrency is not well integrated
with its object-oriented model. It has focussed on the issue of how to make
protected types extensible and yet avoid the pitfalls of the inheritance anomaly.
The approach adopted has been to introduce the notion of a tagged protected
type which has the same underlying philosophy as normal tagged types.
Although the requirements for extensible protected types are easily articu-
lated, there are many potential solutions. The paper has explored the major
issues and, where appropriate, has made concrete proposals. Ada is an extremely
expressive language with many orthogonal features. The paper has
shown that the introduction of extensible protected types does not undermine
that orthogonality, and that the proposal fits in well with limited private types,
generics and normal tagged types.
The work presented here, however, has not been without its difficulties. The
major one is associated with overridden entries. It is a fundamental principle
of object-oriented programming that a child object can build upon the functionality
provided by its parent. The child can call its parent to access that
functionality, and therefore extend it. In Ada, calling an entry is a potentially
suspending operation and this is not allowed from within a protected object.
Hence, overriding entries gives a conflict between the object-oriented and the
protected type models. Furthermore, Ada allows an entry to requeue a call to
another entry. When the requeued entry is serviced, control is not returned
to the entry which issued the requeue request. Consequently, if a parent entry
issues a requeue, control is never returned to the child. This again causes a
conflict with the object-oriented programming model, where a child is allowed
to undertake post-processing after a parent call. The paper has discussed these
conflicts in detail and has proposed a range of potential compromise solutions.
Ada 95 is an important language - the only international standard for object-oriented
real-time distributed programming. It is important that it continues to
evolve. This paper has tried to contribute to the growing debate of how best to
fully integrate the protected type model of Ada into the object-oriented model.
It is clear that introducing extensible protected types is a large change to Ada
and one that is only acceptable at the next major revision of the language. Many
of the complications come from the ability to override entries. One possible
major simplification of the proposal made here would be not to allow these
facilities. Entries would be considered 'final' (using Java terminology). Such a
simplification might lead to an early transition path between current Ada and
a more fully integrated version.

Acknowledgements

The authors gratefully acknowledge the contributions of Oliver Kiddle and
Kristina Lundqvist to the ideas discussed in this paper. We also would like to
acknowledge the participants at the 9th International Workshop on Real-Time
Ada Issues who gave us some feedback on some of our initial ideas.



--R

Integrating Inheritance and Synchronisation in Ada9X
Evaluating synchronisation mechanisms
Structured multiprogramming
Concurrency in Ada
Toward a method of object-oriented concurrent program- ming
Parellism in object-oriented programming languages
Inheritance of synchronization constraints in cocur- rent object-oriented programming languages
Linearizability: A correctness criterion for concurrent objects


Extended protected types
Concurrent Programming in Java
A behavioral notion of subtyping
DRAGOON: An Ada-based object oriented language for concurrent
Analysis of inheritance anomaly in object-oriented concurrent programming languages
Systematic concurrent object-oriented programming

Extendable dispatchable task communication mechanisms

The classiC programming language and design of synchronous concurrent object oriented languages
Java Thread

The programming language Oberon
Parallelism in object-oriented languages: a survey
Concurrent programming in concurrents- malltalk

--TR
Object-oriented concurrent programming ABCL/1
Concurrent programming in concurrent Smalltalk
The programming language Oberon
Linearizability: a correctness condition for concurrent objects
Systematic concurrent object-oriented programming
Toward a method of object-oriented concurrent programming
Introducing concurrency to a sequential language
Analysis of inheritance anomaly in object-oriented concurrent programming languages
Integrating inheritance and synchronization in Ada9X
A behavioral notion of subtyping
Concurrency in Ada
Java Threads
Object-oriented software construction (2nd ed.)
Extensible protected types
Concurrency and distribution in object-oriented programming
The ClassiC programming language and design of synchronous concurrent object oriented languages
Extendable, dispatchable task communication mechanisms
Monitors
Structured multiprogramming
Parallelism in Object-Oriented Languages
Inheritance of Synchronization Constraints in Concurrent Object-Oriented Programming Languages
Evaluating synchronization mechanisms

--CTR
Rodrigo Garca Garca , Alfred Strohmeier, Experiences report on the implementation of EPTs for GNAT, ACM SIGAda Ada Letters, v.XXII n.4, December 2002
Albert M. K. Cheng , James Ras, The implementation of the Priority Ceiling Protocol in Ada-2005, ACM SIGAda Ada Letters, v.XXVII n.1, p.24-39, April 2007
Knut H. Pedersen , Constantinos Constantinides, AspectAda: aspect oriented programming for ada95, ACM SIGAda Ada Letters, v.XXV n.4, p.79-92, December 2005
Gustaf Naeser , Kristina Lundqvist , Lars Asplund, Temporal skeletons for verifying time, ACM SIGAda Ada Letters, v.XXV n.4, p.49-56, December 2005
Aaron W. Keen , Tingjian Ge , Justin T. Maris , Ronald A. Olsson, JR: Flexible distributed programming in an extended Java, ACM Transactions on Programming Languages and Systems (TOPLAS), v.26 n.3, p.578-608, May 2004
concurrency;concurrent object-oriented programming;ada 95;inheritance anomaly