Package PROTECTION
(Documentation)


General

Files in this distribution:

protection.ads package Protection specification
protection.adb package Protection body
protection.html (this file) package Protection documentation

If you want examples of using package Protection, you may download package Tracer from Adalog's web site.

Portability

Fully portable to any compiler.
This package uses no compiler specific feature; it uses no feature defined in special need annexes.

Copyright, etc.

Package Protection and its documentation are Copyright © 1998, 2000 ADALOG.

This software is distributed under Adalog's "advertiseware" license. The goal of this license is to make this product widely available as an advertisement for Adalog activities and a demonstration of Adalog's know-how in Ada development, without imposing any constraint on the user, except that code not made by Adalog should not be confused with original code (we cannot accept responsability for your bugs). Therefore:

Rights to use, distribute or modify in any way this software and its documentation are hereby granted, provided:

  1. You do not remove or change the initial comment in the source files that contains the copyright notice and the reference to Adalog's activities. Similarly, you do not remove this copyright notice and the reference to Adalog's activities from this documentation. Additionnal headers in source files, or changes to the documentation are otherwise allowed.
  2. You distribute this documentation with any copy of the software (whether as source or compiled form).
  3. If you make modifications to the software or this documentation, these modifications must be properly identified as additions or modifications not originating from Adalog. If you make a valuable addition, we would appreciate (but do not require) to be kept informed by sending a message to rosen@adalog.fr.

This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Although we do not require it, you are very welcome, if you find this software useful, to send us a note at rosen@adalog.fr. We also welcome critics, suggestions for improvement, etc.

About Adalog

ADALOG is providing training, consultancy and expertise in Ada and related software engineering techniques. For more info about our services:

ADALOG
2 rue du Docteur Lombard
92130 ISSY-LES-MOULINEAUX
FRANCE
E-m: info@adalog.fr
URL: https://www.adalog.fr/en/adalog.html

What package Protection does

This package provides two utilities that can be used independently or in conjunction to provide various protection paradigms.

The Semaphore type

protected type Semaphore is
    entry P;
    procedure V;
    function Holder return Ada.Task_Identification.Task_Id;

    entry Open; 
    --  blocking if Semaphore is closed and still held by another task.
    entry Close;
    --  blocking if Semaphore is open and still held by another task.
    private
    ...
    end Semaphore;

    Closed_Semaphore : exception;
    Semaphore_Error  : exception;

Semaphore is a protected type with the usual P and V procedures: if a task calls P, then no other task can proceed through a call to P until the first task (the one that owns the semaphore) has called the V procedure. It is however a bit more evolved than a traditionnal semaphore in several aspects:

In short, the owner may decide to forbid any further access by other tasks to the semaphore by closing it, but will never be adversely affected by its own closing until the semaphore is released.

The exception Semaphore_Error is raised when the semaphore is incorrectly used, i.e. if V is called by a task that does not currently hold the semaphore, or if V is called when the semaphore is not busy.

The Protected_Call procedure

type Procedure_Access is access procedure;
    type Semaphore_Access is access all Semaphore;
    procedure Protected_Call (Subprogram : Procedure_Access;
    Lock       : Semaphore_Access := null);

This procedure is used to call a provided procedure in abort-deferred mode, i.e. make sure that the given procedure cannot be aborted. Full semantic of the called procedure is preserved, even if an exception is raised.

If an access to a Semaphore object is given as the second parameter, the whole call is protected by a P/V pair, i.e. it will also be protected from concurrent access by several tasks. Note that the same semaphore object can be given to several Protected_Call, even with different procedures. This allows to make sure that all the procedures are executed in mutual exclusion.

The Generic_Protected_Call package

generic
    type Parameter_Type (<>) is private;
    package Generic_Protected_Call is
    type Parametered_Procedure_Access is access procedure (Item : Parameter_Type);
    procedure Protected_Call (Subprogram : Parametered_Procedure_Access;
    Parameter  : Parameter_Type;
    Lock       : Semaphore_Access := null);        
    private
    ...
    end Generic_Protected_Call;

This generic allows to provide the same functionnality as the Protected_Call procedure described above, but when the called procedure needs one argument. The formal type parameter gives the type of the argument to the procedure.

Note that if more than one parameter is needed, you can simply stuff them into a record type and pass it as a single parameter (provided no parameter is unconstrained or limited). If you need a limited parameter, use Generic_Limited_Protected_Call (described next). Otherwise, we suggest that you make a specialized version of Protected_Call that suits your needs. This should be very straightforward by adapting the procedures from this package.

The Generic_Limited_Protected_Call package

generic
    type Parameter_Type (<>) is limited private;
    package Generic_Limited_Protected_Call is
    type Parametered_Procedure_Access is access procedure (Item : Parameter_Type);
    procedure Protected_Call (Subprogram : Parametered_Procedure_Access;
    Parameter  : access Parameter_Type;
    Lock       : Semaphore_Access := null);        
    private
    ...
    end Generic_Limited_Protected_Call;

This package works exactly like the previous one, but allows the parameter type to be a limited type. The price to pay is that the parameter must be given to the Protected_Call procedure as an access value, therefore needing aliasing etc. Admitedly, whether you should prefer this one or the other one is a matter of need for limitedness and taste...

Questions and answers

Q: Why use Protected_Call rather than a protected type?
A: Of course, the simplest way of protecting code from abortion is to put it into a protected operation. However, there are a number of things (including IOs) that are not allowed in a protected operation: the so-called potentially blocking operations. On the other hand, there is no limitation to what can be put in a procedure protected by Protected_Call. Note also that it is possible to get protection against abortion without serialization, which is not possible with protected operations.

Q: I want to pass a subprocedure of the main program to Protected_Call, but the compiler refuses the 'Access.
A: Since the type Procedure_Access is declared in a library package, only library procedures or procedures declared at the outermost level of a library package can be passed to Protected_Call. See the rules on accessibility levels.

Q: Why does the compiler refuse to instantiate the generic packages from within the main program?
A: The generic packages use controlled types, and can therefore be instantiated only as library packages, or inside library packages.

Q: Where can I find examples of uses of this package?
A: Have a look at package Tracer, also available from Adalog's components page. Actually, the need for package Protection appeared when designing Tracer, and we decided later to make it a software component of its own.

Implementation notes

This section gives additionnal details about the implementation technique of package Protection. We recommend that you first have a look at the implementation of Protection before reading this section. YOU DO NOT NEED TO UNDERSTAND (not even read) THIS SECTION IN ORDER TO USE THE PACKAGE.

The semaphore

The semaphore is a quite straightforward protected type. The only problem we encountered is that we wanted to block or not, depending on the calling task; however, the 'Caller attribute cannot be used in the guard of a protected entry. We solved it by having a public entry with a True guard which checks the condition, and requeues to a private entry if the condition is not met. This technique can be used each time a condition is necessary, which for some reason is too complicated to put as a regular guard.

The Protected_Call

The whole trick for making a call unabortable without protected types is to issue it from a Finalize, since finalization is an abort deferred operation (see RM 9.8(10)). However, a finalizable type (and its Finalize) has to be declared directly in a library package. How could the Finalize be passed the pointer to the procedure to call? The trick was to use an access-to-procedure discriminant in the controlled type (Anti_Abort_Object) whose finalization performs the call. Similarly, the (possible) semaphore is passed as another discriminant.

At this point, the functionnality could seem sufficient. However, if the called procedure raised an exception, it would propagate out of the Finalize, resulting in a bounded error (extremely bounded however, since it always results in the raising of Program_Error, RM 7.6.1(14..24)). Of course, we could have required the called procedure not to raise any exception, but we found that it was actually possible to preserve the full semantics of the original exception. The trick was to add another discriminant which is a pointer to an object of type Exception_Occurrence. If an exception is raised, it is caught in the Finalize (to prevent propagation), but the occurrence is saved. After the finalization, Protected_Call reraises the occurrence. And if no exception occurs ? The Exception_Occurrence object is initialized to Null_Occurrence, and the RM explicitely states (RM 11.4.1(14) that raising a Null_Occurrence does nothing, so it's OK.

The generic versions work exactly like the non-generic one, with the addition of another access discriminant to pass the parameter value to the called procedure. Note however that it is not allowed to take directly the 'Access of a formal parameter. In the non-limited version, we copy the parameter to an (aliased) local object; in the limited version, we require the caller to pass directly an access value.

A final note...

If you found this package useful...
If you think that it would have taken an awful time to write it yourself...
If it showed you some usages of Ada that you didn't think about...

Maybe you should consider using Adalog's consulting and training services !