Ada 95 Quality and Style Guide Chapter 6
Back to Sections 6.0 through 6.2.2
6.2.3 Attributes 'Count, 'Callable, and 'Terminated
guideline
- Do not depend on the values of the task
attributes 'Callable or 'Terminated (Nissen
and Wallis 1984).
- Do not depend on attributes to avoid Tasking_Error
on an entry call.
- For tasks, do not depend on the value of the entry
attribute 'Count.
- Using the 'Count attribute with protected entries is
more reliable than using the 'Count attribute with task
entries.
example
rationale
6.2.4 Unprotected Shared Variables
guideline
- Use calls on protected subprograms or entries to pass
data between tasks rather than unprotected shared
variables.
- Do not use unprotected shared variables as a task
synchronization device.
- Do not reference nonlocal variables
in a guard.
- If an unprotected shared variable is necessary, use the pragma
Volatile or Atomic.
example
rationale
- Ada tasks
- An Ada program and concurrent non-Ada processes
- An Ada program and hardware devices
If your environment supports the Systems Programming Annex (Ada
Reference Manual 1995, Annex C), you should indicate whether loads
and stores to the shared object must be indivisible. If you specify
the pragma Atomic, make sure that the object meets the
underlying hardware requirements for size and alignment.
Multiple tasks sharing the predefined random number generator
and certain input/output subprograms can lead to problems with
unprotected updates to shared state. The Ada Reference Manual
(1995, §A.5.2) points out the need for tasks to synchronize
their access to the random number generators (packages Ada.Numerics.Float_Random
and Ada.Numerics.Discrete_Random). See Guideline 7.7.5
for the I/O issue.
6.2.5 Selective Accepts and Entry Calls
guideline
- Use caution with conditional
entry calls to task entries.
- Use caution with selective accepts
with else parts.
- Do not depend upon a particular delay in timed
entry calls to task entries.
- Do not depend upon a particular delay in selective accepts
with delay alternatives.
- Consider using protected objects instead of the rendezvous for
data-oriented synchronization.
example
rationale
6.2.6 Communication Complexity
guideline
- Minimize the number of accept
and select statements
per task.
- Minimize the number of accept statements per entry.
example
rationale
notes
6.3 TERMINATION
The ability of tasks to interact with each other using Ada's intertask
communication features makes it especially important to manage
planned or unplanned (e.g., in response to a catastrophic exception
condition) termination in a disciplined way. To do otherwise can
lead to a proliferation of undesired and unpredictable side effects
as a result of the termination of a single task.
The guidelines on termination focus on the termination of tasks.
Wherever possible, you should use protected objects (see Guideline
6.1.1), thus avoiding the termination problems associated with
tasks.
6.3.1 Avoiding Undesired Termination
guideline
- Consider using an exception
handler for a rendezvous
within the main loop inside each task.
example
rationale
notes
6.3.2 Normal Termination
guideline
- Do not create nonterminating
tasks unintentionally.
- Explicitly shut down tasks that depend on library packages.
- Confirm that a task is terminated before freeing it with Ada.Unchecked_Deallocation.
- Consider using a select
statement with a terminate alternative rather than an
accept statement alone.
- Consider providing a terminate
alternative for every selective accept that does not
require an else part
or a delay.
- Do not declare or create a task within a user-defined Finalize
procedure after the environment task has finished waiting for
other tasks.
example
rationale
exceptions
6.3.3 The Abort Statement
guideline
- Avoid using the abort statement.
- Consider using the asynchronous select statement rather
than the abort statement.
- Minimize uses of the asynchronous select statement.
- Avoid assigning nonatomic global objects from a task or from
the abortable part of an asynchronous select statement.
example
rationale
6.3.4 Abnormal Termination
guideline
- Place an exception handler
for others at the end
of a task body.
- Consider having each exception handler at the end of a task
body report the task's demise.
- Do not rely on the task status to determine whether a rendezvous
can be made with the task.
example
rationale
6.3.5 Circular Task Calls
guideline
- Do not call a task entry that directly or indirectly results
in a call to an entry of the original calling task.
rationale
6.3.6 Setting Exit Status
guideline
- Avoid race conditions in setting an exit status code from the
main program when using the procedure Ada.Command_Line.Set_Exit_Status.
- In a program with multiple tasks, encapsulate, serialize, and
check calls to the procedure Ada.Command_Line.Set_Exit_Status.
rationale
6.4 SUMMARY
concurrency options
- Consider using protected objects to provide mutually exclusive
access to data.
- Consider using protected objects to control or synchronize
access to data shared by multiple tasks.
- Consider using protected objects to implement synchronization,
such as a passive resource monitor.
- Consider encapsulating protected objects in the private
part or body of a package.
- Consider using a protected procedure to implement an interrupt
handler.
- Do not attach a protected procedure handler to a hardware interrupt
if that interrupt has a maximum priority greater than the ceiling
priority assigned to the handler.
- Avoid the use of global variables in entry barriers.
- Avoid the use of barrier expressions with side effects.
- Use tasks to model selected asynchronous
threads of control within the problem
domain.
- Consider using tasks to define concurrent
algorithms.
- Consider using rendezvous when your application requires synchronous
unbuffered communication.
- Consider using discriminants to minimize the need for an explicit
initialization operation (Rationale 1995, §9.1).
- Consider using discriminants to control composite components
of the protected objects, including
setting the size of an entry family (Rationale 1995, §9.1).
- Consider using a discriminant to set the priority of a protected
object (Rationale 1995, §9.1).
- Consider using a discriminant to identify an interrupt to a
protected object (Rationale 1995, §9.1).
- Consider declaring a task type with a discriminant to indicate
(Rationale 1995, §9.6):
- Priority, storage size, and size of entry families of individual
tasks of a type
- Data associated with a task (through an access discriminant)
- Consider using single task declarations to declare unique instances
of concurrent tasks.
- Consider using single protected declarations to declare unique
instances of protected objects.
- Minimize dynamic creation of tasks because of the potentially
high startup overhead; reuse tasks by having them wait for new
work on some appropriate entry queue.
- Do not rely on pragma Priority
unless your compiler supports the Real-Time Annex (Ada Reference
Manual 1995, Annex D) and priority scheduling.
- Minimize risk of priority inversion by use of protected objects
and ceiling priority.
- Do not rely upon task priorities to achieve a particular sequence
of task execution.
- Do not depend on a particular delay being
achievable (Nissen and Wallis 1984).
- Use a delay until not a delay statement to
delay until a specific time has been reached.
- Avoid using a busy waiting loop
instead of a delay.
- Carefully consider the placement of components of protected
types within a tagged type inheritance hierarchy.
- Consider using generics to provide extensibility of data types
requiring the restrictions provided by protected objects.
communication
- Minimize the work performed during a rendezvous.
- Minimize the work performed in the selective
accept loop of a task.
- Consider using protected objects for data synchronization and
communication.
- Provide a handler for exception
Program_Error
whenever you cannot avoid a selective
accept statement whose alternatives
can all be closed (Honeywell 1986).
- Make systematic use of handlers for Tasking_Error.
- Be prepared to handle exceptions during a rendezvous.
- Consider using a when others exception handler.
- Do not depend on the values of the task
attributes 'Callable or 'Terminated (Nissen
and Wallis 1984).
- Do not depend on attributes to avoid Tasking_Error
on an entry call.
- For tasks, do not depend on the value of the entry
attribute 'Count.
- Using the 'Count attribute with protected entries is
more reliable than using the 'Count attribute with task
entries.
- Use calls on protected subprograms or entries to pass
data between tasks rather than unprotected shared
variables.
- Do not use unprotected shared variables as a task
synchronization device.
- Do not reference nonlocal variables
in a guard.
- If an unprotected shared variable is necessary, use the pragma
Volatile or Atomic.
- Use caution with conditional
entry calls to task entries.
- Use caution with selective accepts
with else parts.
- Do not depend upon a particular delay in timed
entry calls to task entries.
- Do not depend upon a particular delay in selective accepts
with delay alternatives.
- Consider using protected objects instead of the rendezvous for
data-oriented synchronization.
- Minimize the number of accept
and select statements
per task.
- Minimize the number of accept statements per entry.
termination