Rationale for Ada 2012
5.6 Synchronized interfaces and requeue
Ada 2005 introduced interfaces of various kinds:
limited, nonlimited, synchronized, task, and protected. These form a
hierarchy and in particular task and protected interfaces are forms of
synchronized interfaces. The essence of this was to integrate the OO
and real-time features of Ada. But a problem was discovered regarding
requeue as described in a paper presented at IRTAW 2007
[23].
Some examples of interfaces will be found in
[7]
or
[15] where various implementations
of the readers and writers paradigm are explained.
The operations of a
synchronized interface are denoted by subprograms. Thus we might have
package Pkg is
type Server is synchronized interface;
procedure Q(S: in out Server; X: in Item) is abstract;
end Pkg;
We can then implement
the interface by a task type or by a protected type. This introduces
several different ways of implementing the operation Q.
It can be by an entry, or by a protected procedure or by a normal procedure.
For example using a task type we might have
package TP1 is
task type TT1 is new Server with
-- Q implemented by entry
entry Q(X: in Item);
end TT1;
end TP1;
or
package TP2 is
task type TT2 is new Server with
-- Q implemented by a normal procedure
end TT2;
procedure Q(S: in out TT2; X: in Item);
end TP2;
Similarly using a protected
type we might have
package PP1 is
protected type PT1 is new Server with
-- Q implemented by entry
entry Q(X: in Item);
...
end PT1;
end PP1;
or
package PP2 is
protected type PT2 is new Server with
-- Q implemented by a protected procedure
procedure Q(X: in Item);
...
end PT2;
end PP2;}
or
package PP3 is
protected type PT3 is new Server with
-- Q implemented by a normal procedure
...
end PT3;
procedure Q(X: in out PT3; X: in Item);
end PP3;
So the interface Server
could be implemented in many different ways. And as usual we could dispatch
to any of the implementations. We could have
Server_Ptr: access
Server'Class := ...
...
Server_Ptr.Q(X => An_Item);
and this will dispatch to the implementation of Q
concerned.
So a call of Q could end
up as a call of an entry in a task, an entry in a protected object, a
protected procedure in a protected object, or an ordinary procedure.
Two curious situations
arise. One concerns timed calls. We could write a timed call such as
select
Server_Ptr.Q(An_Item);
or
delay Seconds(10);
end select;
and this will always be acceptable. It will dispatch
to the appropriate operation. If it is an entry then it will be a timed
call. But if it is not an entry then no time-out is possible and so by
default the call will always go ahead.
The other curious situation
concerns requeue. In this case there is no obvious default action. It
is not possible to requeue a procedure call since there is no queue on
which to hang it.
The first proposal to do something about this was
simply not to allow requeue at all on interfaces. And indeed this was
the solution adopted in Ada 2005.
However, this is not really acceptable as explained
in
[23]. The next idea was to raise
some exception if it turned out that the destination was not an entry.
But this was considered unsatisfactory.
So it was concluded
that if we do a requeue then it must be statically checked that it will
dispatch to an entry so that the requeue is possible. The next proposal
was that there should be a pragma Implemented
giving requirements on the operation. Thus we might have
procedure Q(S: in out Server; X: in Item) is abstract;
pragma Implemented(Q, By_Entry);}
and the compiler would ensure that all implementations
of the interface Server did indeed implement
Q by an entry so that requeue would always
work. The other possible values for the pragma were By_Protected_Procedure
and By_Any.
The world changed when
the notion of an aspect was invented and so after much discussion the
final solution is that we there is now an aspect
Synchronization
so we write
procedure Q(S: in out Server; X: in Item) is abstract
with Synchronization => By_Entry;
and we are now assured that we are permitted to do
a requeue on Q for any implementation of Server.
The other possible values for the aspect Synchronization
are By_Protected_Procedure and Optional.
In summary, if the property is By_Entry
then the procedure must be implemented by an entry, if the property is
By_Protected_Procedure then the procedure
must be implemented by a protected procedure, and if the property is
Optional then it can be implemented by an
entry, procedure or protected procedure. Naturally enough, the aspect
cannot be given for a function.
There are a number of rules regarding consistency.
The aspect Synchronization can be applied
to a task interface or protected interface as well as to a synchronized
interface. However, if it is applied to a task interface then the aspect
cannot be specified as By_Protected_Procedure
for obvious reasons.
If a type or interface is created by inheritance
from other interfaces then any Synchronization
properties are also inherited and must be consistent. Thus if one is
By_Entry then the others must also be By_Entry
or Optional.
A final minor improvement
mentioned in the Introduction (see
1.3.4)
concerns renaming. Since the days of Ada 83 it has been possible to rename
an entry as a procedure thus
procedure Write(X: in Item) renames Buffer.Put;
where Put is an entry
in a task Buffer. But in Ada 83 it was not
possible to do a timed call using Write. This
was corrected in Ada 2005 which allows a timed call on a renaming.
Similarly, when requeue was introduced in Ada 95,
it was not possible to do a requeue using Write.
This anomaly is corrected in Ada 2012. So now both timed calls and requeue
are permitted using a renaming of an entry.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: