Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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;
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;
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;}
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
   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.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: