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.
Fully portable to any compiler.
This package uses no compiler specific feature; it uses no
feature defined in special need annexes.
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:
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.
ADALOG is providing training, consultancy and expertise in Ada and related software engineering techniques. For more info about our services:
2 rue du Docteur Lombard 92130 ISSY-LES-MOULINEAUX FRANCE |
E-m: info@adalog.fr
URL: https://www.adalog.fr/en/adalog.html |
This package provides two utilities that can be used independently or in conjunction to provide various protection paradigms.
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:
P
again without blocking; these calls
are counted, and the semaphore is released when the
corresponding number of V
have been called.
This may simplify the writing of procedures that must be
protected against concurrent execution, while still
calling each other (a traditionnal semaphore would dead-lock
in this situation). P
, except
the one that currently owns the semaphore if any,
will receive the exception Closed_Semaphore
.
This is also true of any subsequent call to P
.
A closed semaphore can be opened again. If the semaphore
is currently owned by some other task, Open
and Close
will block until the semaphore is
free again. On the other hand, the task that currently
owns the semaphore may open or close it immediately,
without any effect. The owner may even continue to call P
(and of course V
) without any problem. 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.
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.
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.
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...
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.
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 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 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.
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 !