Language Ref Manual references: 9.5 Entries, Entry Calls, and Accept Statements
Server
which
contains Operation
and Operation2
. The statements after the accept body are
executed before Server can accept additional calls to Operation
or Operation2
.
... loop select accept Operation do -- These statements are executed during rendezvous. -- Both caller and server are blocked during this time. ... end Operation; ... -- These statements are not executed during rendezvous. -- Their execution delays getting back to the accept and -- may be a candidate for another task. or accept Operation_2 do -- These statements are executed during rendezvous. -- Both caller and server are blocked during this time. ... end Operation_2; end select; -- These statements are also not executed during rendezvous, -- Their execution delays getting back to the accept and may -- be a candidate for another task. end loop; |
When work is removed from the accept body and placed later in the selective
wait loop, the additional work may still suspend the caller task. If the
caller task calls entry Operation
again before the server task completes its
additional work, the caller is delayed until the server completes the
additional work. If the potential delay is unacceptable and the additional
work does not need to be completed before the next service of the caller task,
the additional work may form the basis of a new task that will not block the
caller task.
Language Ref Manual references: 9.7 Select Statements, 9.7.1 Selective Waits
Program_Error
whenever you cannot
avoid a selective wait statement whose alternates can all be closed (Honeywell
1986).
Tasking_Error
.
Accelerate: begin Throttle.Increase(Step); exception when Tasking_Error => ... when Constraint_Error | Numeric_Error => ... when Throttle_Too_Wide => ... ... end Accelerate; |
In this select statement, if all the guards happen to be closed, the program
can continue by executing the else part. There is no need for a handler for
Program_Error
. Other exceptions can still be raised while
evaluating the guards or attempting to communicate.
... Guarded: begin select when Condition_1 => accept Entry_1; or when Condition_2 => accept Entry_2; else -- all alternatives closed ... end select; exception when Constraint_Error | Numeric_Error => ... end Guarded; |
In this select statement, if all the guards happen to be closed, exception
Program_Error
will be raised. Other exceptions can still be raised
while evaluating the guards or attempting to communicate.
Guarded: begin select when Condition_1 => accept Entry_1; or when Condition_2 => delay Fraction_Of_A_Second; end select; exception when Program_Error => ... when Constraint_Error | Numeric_Error => ... end Guarded; ... |
Program_Error
is raised if a selective wait statement (select
statement containing accepts) is reached, all of whose alternatives are closed
(i.e., the guards evaluate to False
and there are no alternatives without
guards), unless there is an else part. When all alternatives are closed, the
task can never again progress, so there is by definition an error in its
programming. You must be prepared to handle this error should it occur.
Since an else part cannot have a guard, it can never be closed off as an
alternative action, thus its presence prevents Program_Error
. However, an else
part, a delay alternative, and a terminate alternative are all mutually
exclusive, so you will not always be able to provide an else part. In these
cases, you must be prepared to handle Program_Error
.
The exception Tasking_Error
can be raised in the calling task whenever it
attempts to communicate. There are many situations permitting this. Few of
them are preventable by the calling task.
If an exception is raised during a rendezvous and not handled in the accept statement, it is propagated to both tasks and must be handled in two places. See Guideline 5.8.
Program_Error
at a selective wait. These
involve leaving at least one alternative unguarded, or proving that at least
one guard will evaluate True
under all circumstances. The point here is that
you, or your successors, will make mistakes in trying to do this, so you
should prepare to handle the inevitable exception.Language Ref Manual references: 9.7.1 Selective Waits, 11.2 Exception Handlers, 11.4 Exception Handling, 11.5 Exceptions Raised During Task Communication
'Count
, 'Callable
and 'Terminated
'Callable
or
'Terminated
(Nissen and Wallis 1984).
Tasking_Error
on an entry call.
'Count
.
Intercept'Callable
is a boolean indicating if a call
can be made to the task Intercept
without raising the exception Tasking_Error
.
Launch'Count
indicates the number of callers currently waiting at entry Launch
. Intercept'Terminated
is a boolean indicating if the task Intercept
is
in terminated state.
This task is badly programmed because it relies upon the values of the 'Count
attributes not changing between evaluating and acting upon them.
--------------------------------------------------------------------- task body Intercept is ... select when Launch'Count > 0 and Recall'Count = 0 => accept Launch; ... or accept Recall; ... end select; ... end Intercept; --------------------------------------------------------------------- |
If the following code is preempted between evaluating the condition and initiating the call, the assumption that the task is still callable may no longer be valid.
... if Intercept'Callable then Intercept.Recall; end if; ... |
'Callable
, 'Terminated
, and 'Count
are all subject to race
conditions. Between the time you reference an attribute and the time you take
action the value of the attribute may change. Attributes 'Callable
and
'Terminated
convey reliable information once they become False
and True
,
respectively. If 'Callable
is False
, you can expect the callable state to
remain constant. If 'Terminated
is True
, you can expect the task to remain
terminated. Otherwise, 'Terminated
and 'Callable
can change between the time
your code tests them and the time it responds to the result.
The Ada Language Reference Manual (Department of Defense 1983) itself warns
about the asynchronous increase and decrease of the value of 'Count
. A task
can be removed from an entry queue due to execution of an abort statement as
well as expiration of a timed entry call. The use of this attribute in guards
of a selective wait statement may result in the opening of alternatives which
should not be opened under a changed value of 'Count
.
Language Ref Manual references: 9.4 Task Dependence - Termination of Tasks, 9.9 Task and Entry Attributes, A Predefined Language Attributes
--------------------------------------------------------------------- task body Robot_Arm_Driver is Current_Command : Robot_Command; begin -- Robot_Arm_Driver loop Current_Command := Command; -- send to device end loop; ... end Robot_Arm_Driver; --------------------------------------------------------------------- task body Stream_Server is begin loop Stream_Read(Stream_File, Command); end loop; ... end Stream_Server; --------------------------------------------------------------------- |
This code ensures that a missile cannot be fired unless the doors are open and that the missile cannot be armed unless the doors are shut. In this case the requirement for arming may be derived from the duration that the door may be open (i.e., arm first, open door, launch, close door).
Doors_Open : Boolean := False; --------------------------------------------------------------------- task body Intercept is begin ... select when Doors_Open = True => accept Launch; ... or when Doors_Open = False => accept Arm; ... end select; ... end Intercept; --------------------------------------------------------------------- --------------------------------------------------------------------- task body Intercept is Local_Doors_Open : Boolean := False; begin -- Intercept ... select when Local_Doors_Open = True => accept Launch; ... or when Local_Doors_Open = False => accept Arm; ... or accept Door_Status (Doors_Open : in Boolean) do Local_Doors_Open := Doors_Open; end Door_Status; end select; ... end Intercept; --------------------------------------------------------------------- |
The first example above has a race condition requiring perfect interleaving of
execution. This code can be made more reliable by introducing a flag that is
set by Spool_Server
and reset by Line_Printer_Driver
. An if (condition flag)
then delay ... else
would be added to each task loop in order to ensure that
the interleaving is satisfied. However, notice that this approach requires a
delay and the associated rescheduling. Presumably this rescheduling overhead
is what is being avoided by not using the rendezvous.
A guard is a conditional select alternative starting with a when (see 9.7.1 in
Department of Defense 1983). The second example above also has a race
condition requiring two different things. First, the task that opens the doors
must open the doors and update Doors_Open
before allowing the intercept task
to continue execution. Second, the run time system evaluation of the guard in
the select statement cannot occur until the Doors_Open
matches the next
anticipated entry call. If the next call will be to ARM
, then you must make
sure that Doors_Open
changes to False
before the Intercept task reevaluates
the select statement. If the select statement is evaluated while Doors_Open
is
True
and Doors_Open
is subsequently set to False
, the select will continue to
wait on the Launch
until a Launch
is received. An alternate approach is to use
Local_Doors_Open
in the example. This guarantees that the guards will be
reevaluated upon a change in the value of Doors_Open
.
Shared
, which presumably has less overhead than the rendezvous. Be
careful to correctly implement a data access synchronization technique.
Without great effort you might get it wrong. Pragma Shared
can serve as an
expedient against poor run time support systems. Do not always use this as an
excuse to avoid the rendezvous because implementations are allowed to ignore
pragma Shared
(Nissen and Wallis 1984). When pragma Shared
is implemented by
compilers, the implementation is not always uniform and can still lead to
nonportable code. Pragma Shared
affects only those objects whose storage and
retrieval are implemented as indivisible operations. Also, pragma Shared
can only be used for variables of scalar or access type.
Shared
. If you must share data, share the
absolute minimum amount necessary, and be especially careful. As always,
encapsulate the synchronization portions of code.The problem is with variables. Constants, such as tables fixed at compile time, may be safely shared between tasks.
Language Ref Manual references: 9.7.1 Selective Waits, 9.11 Shared Variables, B Predefined Language Pragmas
Current_Position
containing entry Request_New_Coordinates
may never execute if this task has a
higher priority than Current_Position
, because this task doesn't release the processing resource.
... loop select Current_Position.Request_New_Coordinates(X, Y); -- calculate target location based on new coordinates ... else -- calculate target location based on last locations ... end select; end loop; ... |
The addition of a delay as shown may allow Current_Position to execute until it reaches an accept for Request_New_Coordinates.
... loop select Current_Position.Request_New_Coordinates(X, Y); -- calculate target location based on new coordinates ... else -- calculate target location based on last locations ... delay Next_Execute - Clock; Next_Execute := Next_Execute + Period; end select; end loop; ... |
The following selective wait with else again does not degenerate into a busy wait loop only because of the addition of a delay statement.
loop delay Next_Execute - Clock; select accept Get_New_Message (Message : in String) do -- copy message to parameters ... end Get_New_Message; else -- Don't wait for rendezvous -- perform built in test Functions ... end select; Next_Execute := Next_Execute + Task_Period; end loop; |
The following timed entry call may be considered an unacceptable implementation if lost communications with the reactor for over 25 milliseconds results in a critical situation.
... loop select Reactor.Status(OK); or delay 0.025; -- lost communication for more that 25 milliseconds Emergency_Shutdown; end select; -- process reactor status ... end loop; ... |
In the following "selective wait with delay" example, the accuracy of the
coordinate calculation function is bounded by time. For example, the required
accuracy cannot be obtained unless Period
is within +
or -
0.005 seconds. This
period cannot be guaranteed because of the inaccuracy of the delay statement.
... loop select accept Request_New_Coordinates (X : out Integer; Y : out Integer) do -- copy coordinates to parameters ... end Request_New_Coordinates; or delay Next_Execute - Calendar.Clock; end select; Next_Execute := Next_Execute + Period; -- Read Sensors -- execute coordinate transformations end loop; ... |
These constructs are very much implementation dependent. For conditional entry calls and selective waits with else parts, the Ada Language Reference Manual (Department of Defense 1983) does not define "immediately." For timed entry calls and selective waits with delay alternatives, implementors may have ideas of time that differ from each other and from your own. Like the delay statement, the delay alternative on the select construct might wait longer than the time required (see Guideline 6.1.5).
Language Ref Manual references: 9.6 Delay Statements, Duration, and Time, 9.7.1 Selective Waits, 9.7.2 Conditional Entry Calls, 9.7.3 Timed Entry Calls
accept A; if Mode_1 then -- do one thing else -- Mode_2 -- do something different end if; |
rather than
if Mode_1 then accept A; -- do one thing else -- Mode_2 accept A; -- do something different end if; |
Language Ref Manual references: 9.7 Select Statements