Ada 9X Language Study Note LSN-043-DR Implementing ProtectedRecords in Ada P. N. Hilfinger 19 March 1992 I have been exploring various ways to encapsulate the semantics of protected records (or something like it) in a library package. The idea, of course, is that if this could be done, and if a canonical (slow) Ada package body could be given, then manufacturers of non-real-time Ada compilers could ignore the problem, while real-time vendors could give the package special treatment. If finalization is moved into the core, this may actually be feasible. Admittedly, however, there are disadvantages compared to current embedded-in-the-syntax protected records. However, I did at least want to explore the possibility. My exploration is at a flaky, preliminary stage, and given how little time is available to me for Ada activities, may remain so. I offer this, therefore, to whomever cares to do something with it. Before giving the package interface, let's look at a use---the COUNTING_SEMAPHORE example from the MD. I have flagged with "-- * --" lines that contain the gist of this alternate idiom. with PROTECTED_RECORDS; use PROTECTED_RECORDS; -- * -- package SEMAPHORES is type COUNTING_SEMAPHORE(INITIAL_COUNT: INTEGER := 1) is new PROTECTED_RECORD(MAX_BARRIERS => 1) with private; -- * -- function COUNT(S: COUNTING_SEMAPHORE); -- The number of tasks waiting on S. procedure RELEASE(S: in out COUNTING_SEMAPHORE); -- Release a resource (non-suspending). procedure ACQUIRE(S: in out COUNTING_SEMAPHORE); -- Acquire a resource; suspend if none. procedure BARRIERS(S: COUNTING_SEMAPHORE; STATE: out BOOLEANS); -- * -- private type COUNTING_SEMAPHORE(INITIAL_COUNT: INTEGER := 1) is new PROTECTED_RECORD(MAX_BARRIERS => 1) with record -- * -- CURRENT_COUNT: INTEGER := INITIAL_COUNT; end record; end SEMAPHORES; package body SEMAPHORES is ACQUIRE_GUARD: constant := 1; -- * -- procedure BARRIERS(S: COUNTING_SEMAPHORE; STATE: out BOOLEANS) is -- * -- begin -- * -- STATE := (ACQUIRE_GUARD => S.CURRENT_COUNT > 1); -- * -- end; -- * -- function COUNT(S: COUNTING_SEMAPHORE) G: READ_GUARDIAN(+S); -- * -- begin return S.CURRENT_COUNT; end; procedure RELEASE(S: in out COUNTING_SEMAPHORE) G: GUARDIAN(+S); -- * -- begin S.CURRENT_COUNT := S.CURRENT_COUNT + 1; end; procedure ACQUIRE(S: in out COUNTING_SEMAPHORE) G: GUARDIAN(+S, ACQUIRE_GUARD); -- * -- begin S.CURRENT_COUNT := S.CURRENT_COUNT - 1; end; end SEMAPHORES; The declarations of GUARDIANs and READ_GUARDIANs in the subprograms constitute the "trick" (or "kludge") that (allegedly) makes this work. The idea is the initialization and finalization routines for the G's do the work of contending for the lock and waiting in queues. They are attached to the appropriate protected record, in turn, by way of the +S type argument (which turns out to be a pointer to an artificial type introduced to make this syntax work). The BARRIERS routine is overridden by the definer of COUNTING_SEMAPHORE to decide what barriers are open. It, in turn, is called where appropriate in the initialization routine for PROTECTED_RECORD (a tagged type) and in the finalization routines for GUARDIANs and READ_GUARDIANs. The package interface looks something like this. with FINALIZATION_SUPPORT; package PROTECTED_RECORDS is type PROTECTED_RECORD(MAX_BARRIERS: NATURAL) is tagged limited private; -- Define a "protected record" as "any type derived from PROTECTED_RECORD." -- Each protected record has associated with it a set of "barriers", indexed -- from 1 to MAX_BARRIERS. A barrier is either "open" or "closed"; -- initially, all are closed. type BOOLEANS is array (NATURAL range <>) of BOOLEAN; procedure BARRIERS(PR: PROTECTED_RECORD; STATE: out BOOLEANS); -- For each I in STATE'RANGE, BARRIERS must set STATE(I) to TRUE iff -- barrier I is to be opened. Elements of STATE that it does not set -- or that it sets to FALSE indicate closed barriers. The default -- routine does nothing. The BARRIERS routine is not intended to be -- called directly, but will be called automatically at appropriate -- points by other functions. It must be overridden for any protected -- record type with non-trivial barriers. It should open those -- barriers indicated by the state of PR. type PROTECTED_RECORD_LOCK is limited private; type LOCK_HANDLE is access PROTECTED_RECORD_LOCK; function "+"(PR: PROTECTED_RECORD) return LOCK_HANDLE; type GUARDIAN(HANDLE: PROTECTED_HANDLE; BARRIER_INDEX: NATURAL := 0) is tagged limited private; type READ_GUARDIAN(HANDLE: PROTECTED_HANDLE) is tagged limited private; -- A GUARDIAN takes as its discriminant the handle of a lock and the -- index of a barrier. Its finalization action makes sure that the -- lock is released properly and that barriers are recalculated. -- BARRIER_INDEX = 0 implies the absence of a barrier. A READ_GUARDIAN is -- like a GUARDIAN, but manages a read lock. private type PROTECTED_RECORD(MAX_BARRIERS: NATURAL) is is new FINALIZATION_SUPPORT.CONTROLLED with record -- something end record; type GUARDIAN(HANDLE: PROTECTED_HANDLE; BARRIER_INDEX: NATURAL := 0) is new FINALIZATION_SUPPORT.CONTROLLED with record -- something end record; type READ_GUARDIAN(HANDLE: PROTECTED_HANDLE) is new FINALIZATION_SUPPORT.CONTROLLED with record -- something end record; procedure INITIALIZE(OBJ: in out PROTECTED_RECORD); procedure FINALIZE(OBJ: in out PROTECTED_RECORD); procedure INITIALIZE(OBJ: in out GUARDIAN); procedure FINALIZE(OBJ: in out GUARDIAN); procedure INITIALIZE(OBJ: in out READ_GUARDIAN); procedure FINALIZE(OBJ: in out READ_GUARDIAN); type PROTECTED_RECORD_LOCK is record -- something end record; end PROTECTED_RECORDS; I have not supplied a body, and it is possible that the devil in the details makes this unimplementable. Nevertheless, it somehow seems close. Even without the body, some disadvantages should be obvious. 1. The implementor of COUNTING_SEMAPHORE must be sure to put in a GUARDIAN in the right places, with correct arguments. On the other hand, this idiom seems easy enough to check by extra-linguistic means. Also, there is a possibility for abuse of LOCK_HANDLEs, etc. Again, this is not likely to happen accidentally. 2. The need for MAX_BARRIERS and the use of integer indices for the barriers is unfortunate. A generic that introduced an enumeration might help, but I haven't looked into it. 3. Barriers are evaluated at a particular place (when exiting an operation). This is not exactly as in the current PR proposal, although I suspect that it seldom matters semantically. Certain optimization opportunities for barrier evaluation in PRs turn out to be probably infeasible with this package. I don't know their importance either; it hinges on the typical numbers of and complexities of barriers. The interested reader is invited to try to find a body for this package.