||(this file) package
If you want examples of using package
you may download package
Debug from Adalog's web
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 email@example.com. 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:
|19-21 rue du 8 mai 1945
|Tel: +33 1 41 24 31 40
Fax: +33 1 41 24 07 36
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
V procedures: if a task calls
then no other task can proceed through a call to
until the first task (the one that owns the semaphore) has called
V procedure. It is however a bit more evolved
than a traditionnal semaphore in several aspects:
Pagain without blocking; these calls are counted, and the semaphore is released when the corresponding number of
Vhave 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,
Closewill 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.
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
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
pair, i.e. it will also be protected from concurrent access by
several tasks. Note that the same semaphore object can be given
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
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
next). Otherwise, we suggest that you make a specialized version
Protected_Call that suits your needs. This should
be very straightforward by adapting the procedures from this
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
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
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
Note also that it is possible to get protection against abortion
without serialization, which is not possible with protected
Q: I want to pass a subprocedure of the main program to
Protected_Call, but the compiler refuses the 'Access.
A: Since the type
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
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
available from Adalog's
components page. Actually, the need for package
appeared when designing
Debug, and we decided later
to make it a software component of its own.
This section gives additionnal details about the
implementation technique of package
recommend that you first have a look at the implementation of
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
attribute cannot be used in the guard of a protected entry. We
solved it by having a public entry with a
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
since finalization is an abort deferred operation (see RM 9.8(10)).
However, a finalizable type (and its
to be declared directly in a library package. How could the
be passed the pointer to the procedure to call? The trick was to
use an access-to-procedure discriminant in the controlled type (
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
Protected_Call reraises the occurrence.
And if no exception occurs ? The
object is initialized to
Null_Occurrence, and the RM
explicitely states (RM 11.4.1(14) that raising a
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 !