9.4 Protected Units and Protected Objects
{protected object}
{protected operation}
{protected subprogram}
{protected entry}
A
protected object provides coordinated access
to shared data, through calls on its visible
protected operations,
which can be
protected subprograms or
protected entries.
{protected declaration} {protected
unit} {protected
declaration} A
protected unit is
declared by a
protected declaration, which has a corresponding
protected_body.
A protected declaration may be a
protected_type_declaration,
in which case it declares a named protected type; alternatively, it may
be a
single_protected_declaration,
in which case it defines an anonymous protected type, as well as declaring
a named protected object of that type.
{broadcast
signal: See protected object}
Syntax
Reason: We allow the operations and components
to be mixed because that's how other things work (for example, package
declarations). We have relaxed the ordering rules for the items inside
declarative_parts
and
task_definitions
as well.
Legality Rules
This paragraph
was deleted.
Static Semantics
Proof: Private part is defined in Section
8.
{
AI95-00345-01}
{
AI95-00397-01}
{
AI95-00399-01}
{
AI95-00419-01}
For a protected declaration with an
interface_list,
the protected type inherits user-defined primitive subprograms from each
progenitor type (see
3.9.4), in the same
way that a derived type inherits user-defined primitive subprograms from
its progenitor types (see
3.4). If the first
parameter of a primitive inherited subprogram is of the protected type
or an access parameter designating the protected type, and there is a
protected_operation_declaration
for a protected subprogram or single entry with the same identifier within
the protected declaration, whose profile is type conformant with the
prefixed view profile of the inherited subprogram, the inherited subprogram
is said to be
implemented by the conforming protected subprogram
or entry.
{implemented (by a protected
subprogram) [partial]} {implemented
(by a protected entry) [partial]} {type
conformance (required)}
Ramification: The inherited subprograms
can only come from an interface given as part of the protected declaration.
Legality Rules
{
AI95-00345-01}
{requires a completion (protected_declaration})
[partial]} A protected declaration requires
a completion[, which shall be a
protected_body,]
and every
protected_body
shall be the completion of some protected declaration.
To be honest: The completion can be a
pragma Import,
if the implementation supports it.
Proof: 3.9.4
requires that an
interface_list
only name interface types, and limits the descendants of the various
kinds of interface types. Only a limited, protected, or synchronized
interface can have a protected type descendant. Nonlimited or task interfaces
are not allowed, as they offer operations that a protected type does
not have.
{
AI95-00397-01}
The prefixed view profile of an explicitly declared primitive subprogram
of a tagged protected type shall not be type conformant with any protected
operation of the protected type, if the first parameter of the subprogram
is of the protected type or is an access parameter designating the protected
type.
{type conformance (required)}
Reason: This prevents the existence of
two operations with the same name and profile which could be called with
a prefixed view. If the operation was inherited, this would be illegal
by the following rules; this rule puts inherited and non-inherited routines
on the same footing. Note that this only applies to tagged protected
types (that is, those with an interface in their declaration); we do
that as there is no problem with prefixed view calls of primitive operations
for “normal” protected types, and having this rule apply
to all protected types would be incompatible with Ada 95.
{
AI95-00345-01}
{
AI95-00399-01}
For each primitive subprogram inherited by the type declared by a protected
declaration, at most one of the following shall apply:
{
AI95-00345-01}
the inherited subprogram is overridden with a primitive subprogram of
the protected type, in which case the overriding subprogram shall be
subtype conformant with the inherited subprogram and not abstract; or
{subtype
conformance (required)}
{
AI95-00345-01}
{
AI95-00397-01}
the inherited subprogram is implemented by a protected subprogram or
single entry of the protected type, in which case its prefixed view profile
shall be subtype conformant with that of the protected subprogram or
entry.
{subtype conformance (required)}
If neither applies, the inherited subprogram shall
be a null procedure.
{generic contract
issue [partial]} In addition to the places
where Legality Rules normally apply (see
12.3),
these rules also apply in the private part of an instance of a generic
unit.
Reason: Each inherited subprogram can
only have a single implementation (either from overriding a subprogram,
implementing a subprogram, or implementing an entry), and must have an
implementation unless the subprogram is a null procedure.
{
AI95-00345-01}
If an inherited subprogram is implemented by a protected procedure or
an entry, then the first parameter of the inherited subprogram shall
be of mode
out or
in out, or an access-to-variable parameter.
Reason: For a protected procedure or
entry, the protected object can be read or written (see
9.5.1}.
A subprogram that is implemented by a protected procedure or entry must
have a profile which reflects that in order to avoid confusion.
if the
overriding_indicator
is
overriding, then the subprogram shall implement an inherited
subprogram;
if the
overriding_indicator
is
not overriding, then the subprogram shall not implement any
inherited subprogram.
{generic contract
issue [partial]} In addition to the places
where Legality Rules normally apply (see
12.3),
these rules also apply in the private part of an instance of a generic
unit.
Discussion: These rules are subtly different
than those for subprograms (see
8.3.1) because
there cannot be “late” inheritance of primitives from interfaces.
Hidden (that is, private) interfaces are prohibited explicitly (see
7.3),
as are hidden primitive operations (as private operations of public abstract
types are prohibited — see
3.9.3).
Dynamic Semantics
[
{elaboration (protected
declaration) [partial]} The elaboration
of a protected declaration elaborates the
protected_definition.
{elaboration (single_protected_declaration)
[partial]} The elaboration of a
single_protected_declaration
also creates an object of an (anonymous) protected type.]
[
{initialization
(of a protected object) [partial]} As
part of the initialization of a protected object, any per-object constraints
(see
3.8) are elaborated.]
Discussion: We do not mention pragmas
since each pragma has its own elaboration rules.
{elaboration (protected_body)
[partial]} The elaboration of a
protected_body
has no other effect than to establish that protected operations of the
type can from then on be called without failing the Elaboration_Check.
The content of an object
of a given protected type includes:
The values of the components of the protected object,
including (implicitly) an entry queue for each entry declared for the
protected object;
Ramification: "For each entry"
implies one queue for each single entry, plus one for each entry of each
entry family.
{execution resource
(associated with a protected object) [partial]} A
representation of the state of the execution resource
associated
with the protected object (one such resource is associated with each
protected object).
[The execution resource associated with a protected
object has to be acquired to read or update any components of the protected
object; it can be acquired (as part of a protected action — see
9.5.1) either for concurrent read-only access,
or for exclusive read-write access.]
{finalization (of
a protected object) [partial]} {Program_Error
(raised by failure of run-time check)} As
the first step of the
finalization of a protected object, each
call remaining on any entry queue of the object is removed from its queue
and Program_Error is raised at the place of the corresponding
entry_call_statement.
Reason: This
is analogous to the raising of Tasking_Error in callers of a task that
completes before accepting the calls. This situation can only occur due
to a requeue (ignoring premature unchecked_deallocation), since any task
that has accessibility to a protected object is awaited before finalizing
the protected object. For example:
procedure Main is
task T is
entry E;
end T;
task body T is
protected PO is
entry Ee;
end PO;
protected body PO is
entry Ee when False is
begin
null;
end Ee;
end PO;
begin
accept E do
requeue PO.Ee;
end E;
end T;
begin
T.E;
end Main;
The environment task is queued on PO.EE when
PO is finalized.
In a real example, a server task might park
callers on a local protected object for some useful purpose, so we didn't
want to disallow this case.
Bounded (Run-Time) Errors
{
AI95-00280-01}
{bounded error (cause) [partial]}
It is a bounded error to call an entry or subprogram
of a protected object after that object is finalized. If the error is
detected, Program_Error is raised. Otherwise, the call proceeds normally,
which may leave a task queued forever.
Reason: This is very similar to the finalization
rule. It is a bounded error so that an implementation can avoid the overhead
of the check if it can ensure that the call still will operate properly.
Such an implementation cannot need to return resources (such as locks)
to an executive that it needs to execute calls.
This case can
happen (and has happened in production code) when a protected object
is accessed from the Finalize routine of a type. For example:
with Ada.Finalization.Controlled;
package Window_Manager is
...
type Root_Window is new Ada.Finalization.Controlled with private;
type Any_Window is access all Root_Window;
...
private
...
procedure Finalize (Object : in out Root_Window);
...
end Window_Manager;
package body Window_Manager is
protected type Lock is
entry Get_Lock;
procedure Free_Lock;
...
end Lock;
Window_Lock : Lock;
procedure Finalize (Object : in out Root_Window) is
begin
Window_Lock.Get_Lock;
...
Window_Lock.Free_Lock;
end Finalize;
...
A_Window : Any_Window := new Root_Window;
end Window_Manager;
The environment task will call Window_Lock for
the object allocated for A_Window when the collection for Any_Window
is finalized, which will happen after the finalization of Window_Lock
(because finalization of the package body will occur before that of the
package specification).
13 {
AI95-00382-01}
Within the declaration or body of a protected unit other than in an
access_definition,
the name of the protected unit denotes the current instance of the unit
(see
8.6), rather than the first subtype of
the corresponding protected type (and thus the name cannot be used as
a
subtype_mark).
14 A
selected_component
can be used to denote a discriminant of a protected object (see
4.1.3).
Within a protected unit, the name of a discriminant of the protected
type denotes the corresponding discriminant of the current instance of
the unit.
16 The bodies of the protected operations
given in the
protected_body
define the actions that take place upon calls to the protected operations.
17 The declarations in the private part
are only visible within the private part and the body of the protected
unit.
Reason: Component_declarations
are disallowed in a
protected_body
because, for efficiency, we wish to allow the compiler to determine the
size of protected objects (when not dynamic); the compiler cannot necessarily
see the body. Furthermore, the semantics of initialization of such objects
would be problematic — we do not wish to give protected objects
complex initialization semantics similar to task activation.
The same applies to
entry_declarations,
since an entry involves an implicit component — the entry queue.
Examples
Example of declaration
of protected type and corresponding body:
protected type Resource is
entry Seize;
procedure Release;
private
Busy : Boolean := False;
end Resource;
protected body Resource is
entry Seize when not Busy is
begin
Busy := True;
end Seize;
procedure Release is
begin
Busy := False;
end Release;
end Resource;
Example of a single
protected declaration and corresponding body:
protected Shared_Array is
-- Index, Item, and Item_Array are global types
function Component (N : in Index) return Item;
procedure Set_Component(N : in Index; E : in Item);
private
Table : Item_Array(Index) := (others => Null_Item);
end Shared_Array;
protected body Shared_Array is
function Component(N : in Index) return Item is
begin
return Table(N);
end Component;
procedure Set_Component(N : in Index; E : in Item) is
begin
Table(N) := E;
end Set_Component;
end Shared_Array;
Examples of protected
objects:
Control : Resource;
Flags : array(1 .. 100) of Resource;
Extensions to Ada 83
{
extensions to Ada 83}
This
entire clause is new; protected units do not exist in Ada 83.
Extensions to Ada 95
Wording Changes from Ada 95
{
8652/0009}
{
AI95-00137-01}
Corrigendum: Changed representation clauses to aspect clauses
to reflect that they are used for more than just representation.
{
AI95-00280-01}
Described what happens when an operation of a finalized protected object
is called.
{
AI95-00287-01}
Revised the note on operations of protected types to reflect that limited
types do have an assignment operation, but not copying (
assignment_statements).
{
AI95-00382-01}
Revised the note on use of the name of a protected type within itself
to reflect the exception for anonymous access types.