[Home] [Prev] [Next] [Index]

9.5 Intertask Communication

9.5 Intertask Communication

1
The primary means for intertask communication is provided by calls on entries and protected subprograms. Calls on protected subprograms allow coordinated access to shared data objects. Entry calls allow for blocking the caller until a given condition is satisfied (namely, that the corresponding entry is open - see 9.5.3), and then communicating data or control information directly with another task or indirectly via a shared protected object.

Static Semantics

2
Any call on an entry or on a protected subprogram identifies a target object for the operation, which is either a task (for an entry call) or a protected object (for an entry call or a protected subprogram call). The target object is considered an implicit parameter to the operation, and is determined by the operation name (or prefix) used in the call on the operation, as follows:

3 ·
If it is a direct_name or expanded name that denotes the declaration (or body) of the operation, then the target object is implicitly specified to be the current instance of the task or protected unit immediately enclosing the operation; such a call is defined to be an internal call;

4 ·
If it is a selected_component that is not an expanded name, then the target object is explicitly specified to be the task or protected object denoted by the prefix of the name; such a call is defined to be an external call;

4.a
Discussion:  For example:

4.b
protected type Pt is
  procedure Op1;
  procedure Op2;
end Pt;

4.c
PO : Pt;
Other_Object : Some_Other_Protected_Type;

4.d
protected body Pt is
  procedure Op1 is begin ... end Op1;

4.e
  procedure Op2 is
  begin
    Op1; --An internal call.
    Pt.Op1; --Another internal call.
    PO.Op1; --An external call.  It the current instance is PO, then
            --this is a bounded error (see 9.5.1).
    Other_Object.Some_Op; --An external call.
  end Op2;
end Pt;

5 ·
If the name or prefix is a dereference (implicit or explicit) of an access-to-protected-subprogram value, then the target object is determined by the prefix of the Access attribute_reference that produced the access value originally, and the call is defined to be an external call;

6 ·
If the name or prefix denotes a subprogram_renaming_declaration, then the target object is as determined by the name of the renamed entity.

7
A corresponding definition of target object applies to a requeue_statement (see 9.5.4), with a corresponding distinction between an internal requeue and an external requeue.

Dynamic Semantics

8
Within the body of a protected operation, the current instance (see 8.6) of the immediately enclosing protected unit is determined by the target object specified (implicitly or explicitly) in the call (or requeue) on the protected operation.

8.a
To be honest: The current instance is defined in the same way within the body of a subprogram declared immediately within a protected_body.

9
Any call on a protected procedure or entry of a target protected object is defined to be an update to the object, as is a requeue on such an entry.

9.a
Reason: Read/write access to the components of a protected object is granted while inside the body of a protected procedure or entry. Also, any protected entry call can change the value of the Count attribute, which represents an update. Any protected procedure call can result in servicing the entries, which again might change the value of a Count attribute.

9.5.1 Protected Subprograms and Protected Actions

1
A protected subprogram is a subprogram declared immediately within a protected_definition. Protected procedures provide exclusive read-write access to the data of a protected object; protected functions provide concurrent read-only access to the data.

1.a
Ramification: A subprogram declared immediately within a protected_body is not a protected subprogram; it is an intrinsic subprogram. See 6.3.1, "Conformance Rules".

Static Semantics

2
Within the body of a protected function (or a function declared immediately within a protected_body), the current instance of the enclosing protected unit is defined to be a constant [(that is, its subcomponents may be read but not updated)]. Within the body of a protected procedure (or a procedure declared immediately within a protected_body), and within an entry_body, the current instance is defined to be a variable [(updating is permitted)].

2.a
Ramification: The current instance is like an implicit parameter, of mode in for a protected function, and of mode in out for a protected procedure (or protected entry).

Dynamic Semantics

3
For the execution of a call on a protected subprogram, the evaluation of the name or prefix and of the parameter associations, and any assigning back of in out or out parameters, proceeds as for a normal subprogram call (see 6.4). If the call is an internal call (see 9.5), the body of the subprogram is executed as for a normal subprogram call. If the call is an external call, then the body of the subprogram is executed as part of a new protected action on the target protected object; the protected action completes after the body of the subprogram is executed. [A protected action can also be started by an entry call (see 9.5.3).]

4
A new protected action is not started on a protected object while another protected action on the same protected object is underway, unless both actions are the result of a call on a protected function. This rule is expressible in terms of the execution resource associated with the protected object:

5 ·
Starting a protected action on a protected object corresponds to acquiring the execution resource associated with the protected object, either for concurrent read-only access if the protected action is for a call on a protected function, or for exclusive read-write access otherwise;

6 ·
Completing the protected action corresponds to releasing the associated execution resource.

7
[After performing an operation on a protected object other than a call on a protected function, but prior to completing the associated protected action, the entry queues (if any) of the protected object are serviced (see 9.5.3).]

Bounded (Run-Time) Errors

8
During a protected action, it is a bounded error to invoke an operation that is potentially blocking. The following are defined to be potentially blocking operations:

8.a
Reason: Some of these operations are not directly blocking. However, they are still treated as bounded errors during a protected action, because allowing them might impose an undesirable implementation burden.

9 ·
a select_statement;

10 ·
an accept_statement;

11 ·
an entry_call_statement;

12 ·
a delay_statement;

13 ·
an abort_statement;

14 ·
task creation or activation;

15 ·
an external call on a protected subprogram (or an external requeue) with the same target object as that of the protected action;

15.a
Reason: This is really a deadlocking call, rather than a blocking call, but we include it in this list for simplicity.

16 ·
a call on a subprogram whose body contains a potentially blocking operation.

16.a
Reason: This allows an implementation to check and raise Program_Error as soon as a subprogram is called, rather than waiting to find out whether it actually reaches the potentially blocking operation. This in turn allows the potentially blocking operation check to be performed prior to run time in some environments.

17
If the bounded error is detected, Program_Error is raised. If not detected, the bounded error might result in deadlock or a (nested) protected action on the same target object.

18
Certain language-defined subprograms are potentially blocking. In particular, the subprograms of the language-defined input-output packages that manipulate files (implicitly or explicitly) are potentially blocking. Other potentially blocking subprograms are identified where they are defined. When not specified as potentially blocking, a language-defined subprogram is nonblocking.

NOTES

19 18
If two tasks both try to start a protected action on a protected object, and at most one is calling a protected function, then only one of the tasks can proceed. Although the other task cannot proceed, it is not considered blocked, and it might be consuming processing resources while it awaits its turn.  There is no language-defined ordering or queuing presumed for tasks competing to start a protected action -on a multiprocessor such tasks might use busy-waiting; for monoprocessor considerations, see D.3, "Priority Ceiling Locking".

19.a
Discussion:  The intended implementation on a multi-processor is in terms of "spin locks" - the waiting task will spin.

20 19
The body of a protected unit may contain declarations and bodies for local subprograms.  These are not visible outside the protected unit.

21 20
The body of a protected function can contain internal calls on other protected functions, but not protected procedures, because the current instance is a constant. On the other hand, the body of a protected procedure can contain internal calls on both protected functions and procedures.

22 21
From within a protected action, an internal call on a protected subprogram, or an external call on a protected subprogram with a different target object is not considered a potentially blocking operation.

22.a
Reason: This is because a task is not considered blocked while attempting to acquire the execution resource associated with a protected object.  The acquisition of such a resource is rather considered part of the normal competition for execution resources between the various tasks that are ready. External calls that turn out to be on the same target object are considered potentially blocking, since they can deadlock the task indefinitely.

Examples

23
Examples of protected subprogram calls (see 9.4):

24
Shared_Array.Set_Component(N, E);
E := Shared_Array.Component(M);
Control.Release;

9.5.2 Entries and Accept Statements

1
Entry_declarations, with the corresponding entry_bodies or accept_statements, are used to define potentially queued operations on tasks and protected objects.

Syntax

2
entry_declaration ::=
   entry defining_identifier [(discrete_subtype_definition)] parameter_profile;

3
accept_statement ::=
   accept entry_direct_name [(entry_index)] parameter_profile [do
     handled_sequence_of_statements
   end [entry_identifier]];

3.a
Reason: We cannot use defining_identifier for accept_statements. Although an accept_statement is sort of like a body, it can appear nested within a block_statement, and therefore be hidden from its own entry by an outer homograph.

4
entry_index ::= expression

5
entry_body ::=
  entry defining_identifier  entry_body_formal_part  entry_barrier is
    declarative_part
  begin
    handled_sequence_of_statements
  end [entry_identifier];

6
entry_body_formal_part ::= [(entry_index_specification)] parameter_profile

7
entry_barrier ::= when condition

8
entry_index_specification ::= for defining_identifier in discrete_subtype_definition

9
If an entry_identifier appears at the end of an accept_statement, it shall repeat the entry_direct_name. If an entry_identifier appears at the end of an entry_body, it shall repeat the defining_identifier.

10
[An entry_declaration is allowed only in a protected or task declaration.]

10.a
Proof: This follows from the BNF.

Name Resolution Rules

11
In an accept_statement, the expected profile for the entry_direct_name is that of the entry_declaration; the expected type for an entry_index is that of the subtype defined by the discrete_subtype_definition of the corresponding entry_declaration.

12
Within the handled_sequence_of_statements of an accept_statement, if a selected_component has a prefix that denotes the corresponding entry_declaration, then the entity denoted by the prefix is the accept_statement, and the selected_component is interpreted as an expanded name (see 4.1.3)[; the selector_name of the selected_component has to be the identifier for some formal parameter of the accept_statement].

12.a
Proof: The only declarations that occur immediately within the declarative region of an accept_statement are those for its formal parameters.

Legality Rules

13
An entry_declaration in a task declaration shall not contain a specification for an access parameter (see 3.10).

13.a
Reason: Access parameters for task entries would require a complex implementation. For example:

13.b
task T is
   entry E(Z : access Integer); --Illegal!
end T;

13.c
task body T is
begin
   declare
      type A is access all Integer;
      X : A;
      Int : aliased Integer;
      task Inner;
      task body Inner is
      begin
         T.E(Int'Access);
      end Inner;
   begin
      accept E(Z : access Integer) do
         X := A(Z); -- Accessibility_Check
      end E;
   end;
end T;

13.d
Implementing the Accessibility_Check inside the accept_statement for E is difficult, since one does not know whether the entry caller is calling from inside the immediately enclosing declare block or from outside it.  This means that the lexical nesting level associated with the designated object is not sufficient to determine whether the Accessibility_Check should pass or fail.

13.e
Note that such problems do not arise with protected entries, because entry_bodies are always nested immediately within the protected_body; they cannot be further nested as can accept_statements, nor can they be called from within the protected_body (since no entry calls are permitted inside a protected_body).

14
For an accept_statement, the innermost enclosing body shall be a task_body, and the entry_direct_name shall denote an entry_declaration in the corresponding task declaration; the profile of the accept_statement shall conform fully to that of the corresponding entry_declaration. An accept_statement shall have a parenthesized entry_index if and only if the corresponding entry_declaration has a discrete_subtype_definition.

15
An accept_statement shall not be within another accept_statement that corresponds to the same entry_declaration, nor within an asynchronous_select inner to the enclosing task_body.

15.a
Reason: Accept_statements are required to be immediately within the enclosing task_body (as opposed to being in a nested subprogram) to ensure that a nested task does not attempt to accept the entry of its enclosing task.  We considered relaxing this restriction, either by making the check a run-time check, or by allowing a nested task to accept an entry of its enclosing task.  However, neither change seemed to provide sufficient benefit to justify the additional implementation burden.

15.b
Nested accept_statements for the same entry (or entry family) are prohibited to ensure that there is no ambiguity in the resolution of an expanded name for a formal parameter of the entry.  This could be relaxed by allowing the inner one to hide the outer one from all visibility, but again the small added benefit didn't seem to justify making the change for Ada 9X.

15.c
Accept_statements are not permitted within asynchronous_select statements to simplify the semantics and implementation: an accept_statement in an abortable_part could result in Tasking_Error being propagated from an entry call even though the target task was still callable; implementations that use multiple tasks implicitly to implement an asynchronous_select might have trouble supporting "up-level" accepts. Furthermore, if accept_statements were permitted in the abortable_part, a task could call its own entry and then accept it in the abortable_part, leading to rather unusual and possibly difficult-to-specify semantics.

16
An entry_declaration of a protected unit requires a completion[, which shall be an entry_body,] and every entry_body shall be the completion of an entry_declaration of a protected unit. The profile of the entry_body shall conform fully to that of the corresponding declaration.

16.a
Ramification: An entry_declaration, unlike a subprogram_declaration, cannot be completed with a renaming_declaration.

16.b
To be honest: The completion can be a pragma Import, if the implementation supports it.

16.c
Discussion:  The above applies only to protected entries, which are the only ones completed with entry_bodies. Task entries have corresponding accept_statements instead of having entry_bodies, and we do not consider an accept_statement to be a "completion," because a task entry_declaration is allowed to have zero, one, or more than one corresponding accept_statements.

17
An entry_body_formal_part shall have an entry_index_specification if and only if the corresponding entry_declaration has a discrete_subtype_definition. In this case, the discrete_subtype_definitions of the entry_declaration and the entry_index_specification shall fully conform to one another (see 6.3.1).

18
A name that denotes a formal parameter of an entry_body is not allowed within the entry_barrier of the entry_body.

Static Semantics

19
The parameter modes defined for parameters in the parameter_profile of an entry_declaration are the same as for a subprogram_declaration and have the same meaning (see 6.2).

19.a
Discussion:  Note that access parameters are not allowed for task entries (see above).

20
An entry_declaration with a discrete_subtype_definition (see 3.6) declares a family of distinct entries having the same profile, with one such entry for each value of the entry index subtype defined by the discrete_subtype_definition. [A name for an entry of a family takes the form of an indexed_component, where the prefix denotes the entry_declaration for the family, and the index value identifies the entry within the family.] The term single entry is used to refer to any entry other than an entry of an entry family.

21
In the entry_body for an entry family, the entry_index_specification declares a named constant whose subtype is the entry index subtype defined by the corresponding entry_declaration; the value of the named entry index identifies which entry of the family was called.

21.a
Ramification: The discrete_subtype_definition of the entry_index_specification is not elaborated; the subtype of the named constant declared is defined by the discrete_subtype_definition of the corresponding entry_declaration, which is elaborated, either when the type is declared, or when the object is created, if its constraint is per-object.

Dynamic Semantics

22
For the elaboration of an entry_declaration for an entry family, if the discrete_subtype_definition contains no per-object expressions (see 3.8), then the discrete_subtype_definition is elaborated.  Otherwise, the elaboration of the entry_declaration consists of the evaluation of any expression of the discrete_subtype_definition that is not a per-object expression (or part of one). The elaboration of an entry_declaration for a single entry has no effect.

22.a
Discussion:  The elaboration of the declaration of a protected subprogram has no effect, as specified in clause 6.1. The default initialization of an object of a task or protected type is covered in 3.3.1.

23
[The actions to be performed when an entry is called are specified by the corresponding accept_statements (if any) for an entry of a task unit, and by the corresponding entry_body for an entry of a protected unit.]

24
For the execution of an accept_statement, the entry_index, if any, is first evaluated and converted to the entry index subtype; this index value identifies which entry of the family is to be accepted. Further execution of the accept_statement is then blocked until a caller of the corresponding entry is selected (see 9.5.3), whereupon the handled_sequence_of_statements, if any, of the accept_statement is executed, with the formal parameters associated with the corresponding actual parameters of the selected entry call. Upon completion of the handled_sequence_of_statements, the accept_statement completes and is left. When an exception is propagated from the handled_sequence_of_statements of an accept_statement, the same exception is also raised by the execution of the corresponding entry_call_statement.

24.a
Ramification: This is in addition to propagating it to the construct containing the accept_statement. In other words, for a rendezvous, the raising splits in two, and continues concurrently in both tasks.

24.b
The caller gets a new occurrence; this isn't considered propagation.

24.c
Note that we say "propagated from the handled_sequence_of_statements of an accept_statement", not "propagated from an accept_statement." The latter would be wrong - we don't want exceptions propagated by the entry_index to be sent to the caller (there is none yet!).

25
The above interaction between a calling task and an accepting task is called a rendezvous. [After a rendezvous, the two tasks continue their execution independently.]

26
[An entry_body is executed when the condition of the entry_barrier evaluates to True and a caller of the corresponding single entry, or entry of the corresponding entry family, has been selected (see 9.5.3).] For the execution of the entry_body, the declarative_part of the entry_body is elaborated, and the handled_sequence_of_statements of the body is executed, as for the execution of a subprogram_body.  The value of the named entry index, if any, is determined by the value of the entry index specified in the entry_name of the selected entry call (or intermediate requeue_statement - see 9.5.4).

26.a
To be honest: If the entry had been renamed as a subprogram, and the call was a procedure_call_statement using the name declared by the renaming, the entry index (if any) comes from the entry name specified in the subprogram_renaming_declaration.

NOTES

27 22
A task entry has corresponding accept_statements (zero or more), whereas a protected entry has a corresponding entry_body (exactly one).

28 23
A consequence of the rule regarding the allowed placements of accept_statements is that a task can execute accept_statements only for its own entries.

29 24
A return_statement (see 6.5) or a requeue_statement (see 9.5.4) may be used to complete the execution of an accept_statement or an entry_body.

29.a
Ramification: An accept_statement need not have a handled_sequence_of_statements even if the corresponding entry has parameters.  Equally, it can have a handled_sequence_of_statements even if the corresponding entry has no parameters.

29.b
Ramification: A single entry overloads a subprogram, an enumeration literal, or another single entry if they have the same defining_identifier.  Overloading is not allowed for entry family names. A single entry or an entry of an entry family can be renamed as a procedure as explained in 8.5.4.

30 25
The condition in the entry_barrier may reference anything visible except the formal parameters of the entry. This includes the entry index (if any), the components (including discriminants) of the protected object, the Count attribute of an entry of that protected object, and data global to the protected unit.

31
The restriction against referencing the formal parameters within an entry_barrier ensures that all calls of the same entry see the same barrier value. If it is necessary to look at the parameters of an entry call before deciding whether to handle it, the entry_barrier can be "when True" and the caller can be requeued (on some private entry) when its parameters indicate that it cannot be handled immediately.

Examples

32
Examples of entry declarations:

33
entry Read(V : out Item);
entry Seize;
entry Request(Level)(D : Item);  --  a family of entries

34
Examples of accept statements:

35
accept Shut_Down;

36
accept Read(V : out Item) do
   V := Local_Item;
end Read;

37
accept Request(Low)(D : Item) do
   ...
end Request;

Extensions to Ada 83

37.a
The syntax rule for entry_body is new.

37.b
Accept_statements can now have exception_handlers.

9.5.3 Entry Calls

1
[An entry_call_statement (an entry call) can appear in various contexts.] A simple entry call is a stand-alone statement that represents an unconditional call on an entry of a target task or a protected object. [Entry calls can also appear as part of select_statements (see 9.7).]

Syntax

2
entry_call_statement ::= entry_name [actual_parameter_part];

Name Resolution Rules

3
The entry_name given in an entry_call_statement shall resolve to denote an entry.  The rules for parameter associations are the same as for subprogram calls (see 6.4 and 6.4.1).

Static Semantics

4
[The entry_name of an entry_call_statement specifies (explicitly or implicitly) the target object of the call, the entry or entry family, and the entry index, if any (see 9.5).]

Dynamic Semantics

5
Under certain circumstances (detailed below), an entry of a task or protected object is checked to see whether it is open or closed:

6 ·
An entry of a task is open if the task is blocked on an accept_statement that corresponds to the entry (see 9.5.2), or on a selective_accept (see 9.7.1) with an open accept_alternative that corresponds to the entry; otherwise it is closed.

7 ·
An entry of a protected object is open if the condition of the entry_barrier of the corresponding entry_body evaluates to True; otherwise it is closed. If the evaluation of the condition propagates an exception, the exception Program_Error is propagated to all current callers of all entries of the protected object.

7.a
Reason: An exception during barrier evaluation is considered essentially a fatal error.  All current entry callers are notified with a Program_Error. In a fault-tolerant system, a protected object might provide a Reset protected procedure, or equivalent, to support attempts to restore such a "broken" protected object to a reasonable state.

7.b
Discussion:  Note that the definition of when a task entry is open is based on the state of the (accepting) task, whereas the "openness" of a protected entry is defined only when it is explicitly checked, since the barrier expression needs to be evaluated.  Implementation permissions are given (below) to allow implementations to evaluate the barrier expression more or less often than it is checked, but the basic semantic model presumes it is evaluated at the times when it is checked.

8
For the execution of an entry_call_statement, evaluation of the name and of the parameter associations is as for a subprogram call (see 6.4). The entry call is then issued: For a call on an entry of a protected object, a new protected action is started on the object (see 9.5.1). The named entry is checked to see if it is open; if open, the entry call is said to be selected immediately, and the execution of the call proceeds as follows:

9 ·
For a call on an open entry of a task, the accepting task becomes ready and continues the execution of the corresponding accept_statement (see 9.5.2).

10 ·
For a call on an open entry of a protected object, the corresponding entry_body is executed (see 9.5.2) as part of the protected action.

11
If the accept_statement or entry_body completes other than by a requeue (see 9.5.4), return is made to the caller (after servicing the entry queues - see below); any necessary assigning back of formal to actual parameters occurs, as for a subprogram call (see 6.4.1); such assignments take place outside of any protected action.

11.a
Ramification: The return to the caller will generally not occur until the protected action completes, unless some other thread of control is given the job of completing the protected action and releasing the associated execution resource.

12
If the named entry is closed, the entry call is added to an entry queue (as part of the protected action, for a call on a protected entry), and the call remains queued until it is selected or cancelled; there is a separate (logical) entry queue for each entry of a given task or protected object [(including each entry of an entry family)].

13
When a queued call is selected, it is removed from its entry queue. Selecting a queued call from a particular entry queue is called servicing the entry queue. An entry with queued calls can be serviced under the following circumstances:

14 ·
When the associated task reaches a corresponding accept_statement, or a selective_accept with a corresponding open accept_alternative;

15 ·
If after performing, as part of a protected action on the associated protected object, an operation on the object other than a call on a protected function, the entry is checked and found to be open.

16
If there is at least one call on a queue corresponding to an open entry, then one such call is selected according to the entry queuing policy in effect (see below), and the corresponding accept_statement or entry_body is executed as above for an entry call that is selected immediately.

17
The entry queuing policy controls selection among queued calls both for task and protected entry queues. The default entry queuing policy is to select calls on a given entry queue in order of arrival.  If calls from two or more queues are simultaneously eligible for selection, the default entry queuing policy does not specify which queue is serviced first. Other entry queuing policies can be specified by pragmas (see D.4).

18
For a protected object, the above servicing of entry queues continues until there are no open entries with queued calls, at which point the protected action completes.

18.a
Discussion:  While servicing the entry queues of a protected object, no new calls can be added to any entry queue of the object, except due to an internal requeue (see 9.5.4). This is because the first step of a call on a protected entry is to start a new protected action, which implies acquiring (for exclusive read-write access) the execution resource associated with the protected object, which cannot be done while another protected action is already in progress.

19
For an entry call that is added to a queue, and that is not the triggering_statement of an asynchronous_select (see 9.7.4), the calling task is blocked until the call is cancelled, or the call is selected and a corresponding accept_statement or entry_body completes without requeuing. In addition, the calling task is blocked during a rendezvous.

19.a
Ramification: For a call on a protected entry, the caller is not blocked if the call is selected immediately, unless a requeue causes the call to be queued.

20
An attempt can be made to cancel an entry call upon an abort (see 9.8) and as part of certain forms of select_statement (see 9.7.2, 9.7.3, and 9.7.4). The cancellation does not take place until a point (if any) when the call is on some entry queue, and not protected from cancellation as part of a requeue (see 9.5.4); at such a point, the call is removed from the entry queue and the call completes due to the cancellation. The cancellation of a call on an entry of a protected object is a protected action[, and as such cannot take place while any other protected action is occurring on the protected object. Like any protected action, it includes servicing of the entry queues (in case some entry barrier depends on a Count attribute).]

20.a
Implementation Note: In the case of an attempted cancellation due to abort, this removal might have to be performed by the calling task itself if the ceiling priority of the protected object is lower than the task initiating the abort.

21
A call on an entry of a task that has already completed its execution raises the exception Tasking_Error at the point of the call; similarly, this exception is raised at the point of the call if the called task completes its execution or becomes abnormal before accepting the call or completing the rendezvous (see 9.8). This applies equally to a simple entry call and to an entry call as part of a select_statement.

Implementation Permissions

22
An implementation may perform the sequence of steps of a protected action using any thread of control; it need not be that of the task that started the protected action. If an entry_body completes without requeuing, then the corresponding calling task may be made ready without waiting for the entire protected action to complete.

22.a
Reason: These permissions are intended to allow flexibility for implementations on multiprocessors.  On a monoprocessor, which thread of control executes the protected action is essentially invisible, since the thread is not abortable in any case, and the "current_task" function is not guaranteed to work during a protected action (see C.7).

23
When the entry of a protected object is checked to see whether it is open, the implementation need not reevaluate the condition of the corresponding entry_barrier if no variable or attribute referenced by the condition (directly or indirectly) has been altered by the execution (or cancellation) of a protected procedure or entry call on the object since the condition was last evaluated.

23.a
Ramification: Changes to variables referenced by an entry barrier that result from actions outside of a protected procedure or entry call on the protected object need not be "noticed."  For example, if a global variable is referenced by an entry barrier, it should not be altered (except as part of a protected action on the object) any time after the barrier is first evaluated. In other words, globals can be used to "parameterize" a protected object, but they cannot reliably be used to control it after the first use of the protected object.

23.b
Implementation Note: Note that even if a global variable is volatile, the implementation need only reevaluate a barrier if the global is updated during a protected action on the protected object. This ensures that an entry-open bit-vector implementation approach is possible, where the bit-vector is computed at the end of a protected action, rather than upon each entry call.

24
An implementation may evaluate the conditions of all entry_barriers of a given protected object any time any entry of the object is checked to see if it is open.

24.a
Ramification: In other words, any side-effects of evaluating an entry barrier should be innocuous, since an entry barrier might be evaluated more or less often than is implied by the "official" dynamic semantics.

24.b
Implementation Note: It is anticipated that when the number of entries is known to be small, all barriers will be evaluated any time one of them needs to be, to produce an "entry-open bit-vector."  The appropriate bit will be tested when the entry is called, and only if the bit is false will a check be made to see whether the bit-vector might need to be recomputed.  This should allow an implementation to maximize the performance of a call on an open entry, which seems like the most important case.

24.c
In addition to the entry-open bit-vector, an "is-valid" bit is needed per object, which indicates whether the current bit-vector setting is valid. A "depends-on-Count-attribute" bit is needed per type. The "is-valid" bit is set to false (as are all the bits of the bit-vector) when the protected object is first created, as well as any time an exception is propagated from computing the bit-vector.  Is-valid would also be set false any time the Count is changed and "depends-on-Count-attribute" is true for the type, or a protected procedure or entry returns indicating it might have updated a variable referenced in some barrier.

24.d
A single procedure can be compiled to evaluate all of the barriers, set the entry-open bit-vector accordingly, and set the is-valid bit to true. It could have a "when others" handler to set them all false, and call a routine to propagate Program_Error to all queued callers.

24.e
For protected types where the number of entries is not known to be small, it makes more sense to evaluate a barrier only when the corresponding entry is checked to see if it is open.  It isn't worth saving the state of the entry between checks, because of the space that would be required.  Furthermore, the entry queues probably want to take up space only when there is actually a caller on them, so rather than an array of all entry queues, a linked list of nonempty entry queues make the most sense in this case, with the first caller on each entry queue acting as the queue header.

25
When an attempt is made to cancel an entry call, the implementation need not make the attempt using the thread of control of the task (or interrupt) that initiated the cancellation; in particular, it may use the thread of control of the caller itself to attempt the cancellation, even if this might allow the entry call to be selected in the interim.

25.a
Reason: Because cancellation of a protected entry call is a protected action (which helps make the Count attribute of a protected entry meaningful), it might not be practical to attempt the cancellation from the thread of control that initiated the cancellation.  For example, if the cancellation is due to the expiration of a delay, it is unlikely that the handler of the timer interrupt could perform the necessary protected action itself (due to being on the interrupt level).  Similarly, if the cancellation is due to an abort, it is possible that the task initiating the abort has a priority higher than the ceiling priority of the protected object (for implementations that support ceiling priorities). Similar considerations could apply in a multiprocessor situation.

NOTES

26 26
If an exception is raised during the execution of an entry_body, it is propagated to the corresponding caller (see 11.4).

27 27
For a call on a protected entry, the entry is checked to see if it is open prior to queuing the call, and again thereafter if its Count attribute (see 9.9) is referenced in some entry barrier.

27.a
Ramification: Given this, extra care is required if a reference to the Count attribute of an entry appears in the entry's own barrier.

27.b
Reason: An entry is checked to see if it is open prior to queuing to maximize the performance of a call on an open entry.

28 28
In addition to simple entry calls, the language permits timed, conditional, and asynchronous entry calls (see 9.7.2, 9.7.3, and see 9.7.4).

28.a
Ramification: A task can call its own entries, but the task will deadlock if the call is a simple entry call.

29 29
The condition of an entry_barrier is allowed to be evaluated by an implementation more often than strictly necessary, even if the evaluation might have side effects.  On the other hand, an implementation need not reevaluate the condition if nothing it references was updated by an intervening protected action on the protected object, even if the condition references some global variable that might have been updated by an action performed from outside of a protected action.

Examples

30
Examples of entry calls:

31
Agent.Shut_Down;                      --  see 9.1
Parser.Next_Lexeme(E);                --  see 9.1
Pool(5).Read(Next_Char);              --  see 9.1
Controller.Request(Low)(Some_Item);   --  see 9.1
Flags(3).Seize;                       --  see 9.4

9.5.4 Requeue Statements

1
[A requeue_statement can be used to complete an accept_statement or entry_body, while redirecting the corresponding entry call to a new (or the same) entry queue. Such a requeue can be performed with or without allowing an intermediate cancellation of the call, due to an abort or the expiration of a delay.]

Syntax

2
requeue_statement ::= requeue entry_name [with abort];

Name Resolution Rules

3
The entry_name of a requeue_statement shall resolve to denote an entry (the target entry) that either has no parameters, or that has a profile that is type conformant (see 6.3.1) with the profile of the innermost enclosing entry_body or accept_statement.

Legality Rules

4
A requeue_statement shall be within a callable construct that is either an entry_body or an accept_statement, and this construct shall be the innermost enclosing body or callable construct.

5
If the target entry has parameters, then its profile shall be subtype conformant with the profile of the innermost enclosing callable construct.

6
In a requeue_statement of an accept_statement of some task unit, either the target object shall be a part of a formal parameter of the accept_statement, or the accessibility level of the target object shall not be equal to or statically deeper than any enclosing accept_statement of the task unit. In a requeue_statement of an entry_body of some protected unit, either the target object shall be a part of a formal parameter of the entry_body, or the accessibility level of the target object shall not be statically deeper than that of the entry_declaration.

6.a
Ramification: In the entry_body case, the intent is that the target object can be global, or can be a component of the protected unit, but cannot be a local variable of the entry_body.

6.b
Reason: These restrictions ensure that the target object of the requeue outlives the completion and finalization of the enclosing callable construct. They also prevent requeuing from a nested accept_statement on a parameter of an outer accept_statement, which could create some strange "long-distance" connections between an entry caller and its server.

6.c
Note that in the strange case where a task_body is nested inside an accept_statement, it is permissible to requeue from an accept_statement of the inner task_body on parameters of the outer accept_statement.  This is not a problem because all calls on the inner task have to complete before returning from the outer accept_statement, meaning no "dangling calls" will be created.

6.d
Implementation Note: By disallowing certain requeues, we ensure that the normal terminate_alternative rules remain sensible, and that explicit clearing of the entry queues of a protected object during finalization is rarely necessary.  In particular, such clearing of the entry queues is necessary only (ignoring premature Unchecked_Deallocation) for protected objects declared in a task_body (or created by an allocator for an access type declared in such a body) containing one or more requeue_statements. Protected objects declared in subprograms, or at the library level, will never need to have their entry queues explicitly cleared during finalization.

Dynamic Semantics

7
The execution of a requeue_statement proceeds by first evaluating the entry_name[, including the prefix identifying the target task or protected object and the expression identifying the entry within an entry family, if any]. The entry_body or accept_statement enclosing the requeue_statement is then completed[, finalized, and left (see 7.6.1)].

8
For the execution of a requeue on an entry of a target task, after leaving the enclosing callable construct, the named entry is checked to see if it is open and the requeued call is either selected immediately or queued, as for a normal entry call (see 9.5.3).

9
For the execution of a requeue on an entry of a target protected object, after leaving the enclosing callable construct:

10 ·
if the requeue is an internal requeue (that is, the requeue is back on an entry of the same protected object -  see 9.5), the call is added to the queue of the named entry and the ongoing protected action continues (see 9.5.1);

10.a
Ramification: Note that for an internal requeue, the call is queued without checking whether the target entry is open. This is because the entry queues will be serviced before the current protected action completes anyway, and considering the requeued call immediately might allow it to "jump" ahead of existing callers on the same queue.

11 ·
if the requeue is an external requeue (that is, the target protected object is not implicitly the same as the current object -  see 9.5), a protected action is started on the target object and proceeds as for a normal entry call (see 9.5.3).

12
If the new entry named in the requeue_statement has formal parameters, then during the execution of the accept_statement or entry_body corresponding to the new entry, the formal parameters denote the same objects as did the corresponding formal parameters of the callable construct completed by the requeue. [In any case, no parameters are specified in a requeue_statement; any parameter passing is implicit.]

13
If the requeue_statement includes the reserved words with abort (it is a requeue-with-abort), then:

14 ·
if the original entry call has been aborted (see 9.8), then the requeue acts as an abort completion point for the call, and the call is cancelled and no requeue is performed;

15 ·
if the original entry call was timed (or conditional), then the original expiration time is the expiration time for the requeued call.

16
If the reserved words with abort do not appear, then the call remains protected against cancellation while queued as the result of the requeue_statement.

16.a
Ramification: This protection against cancellation lasts only until the call completes or a subsequent requeue-with-abort is performed on the call.

16.b
Reason: We chose to protect a requeue, by default, against abort or cancellation. This seemed safer, since it is likely that extra steps need to be taken to allow for possible cancellation once the servicing of an entry call has begun.  This also means that in the absence of with abort the usual Ada 83 behavior is preserved, namely that once an entry call is accepted, it cannot be cancelled until it completes.

NOTES

17 30
A requeue is permitted from a single entry to an entry of an entry family, or vice-versa.  The entry index, if any, plays no part in the subtype conformance check between the profiles of the two entries; an entry index is part of the entry_name for an entry of a family.

Examples

18
Examples of requeue statements:

19
requeue Request(Medium) with abort;
                    -- requeue on a member of an entry family of the current task, see 9.1

20
requeue Flags(I).Seize;
                    -- requeue on an entry of an array component, see 9.4

Extensions to Ada 83

20.a
The requeue_statement is new.



[Home] [Prev] [Next] [Index]

documentation@rational.com
Copyright © 1993-1998, Rational Software Corporation.   All rights reserved.