Version 1.6 of ais/ai-00266.txt
!standard D.2.2 (5) 02-03-09 AI95-00266/05
!standard D.7 (00)
!class amendment 01-05-10
!status work item 01-05-10
!status received 01-05-10
!subject Task Termination procedure
A mechanism is proposed for associating a task group object with a task. The
task group object receives notification when the task is about to terminate. If
a task terminates due to an unhandled exception, the exception occurrence is
passed to the group. By default, when a new task is created, the task group of
its master becomes its task group. However, a master may locally specify a
different "creation" group for its subtasks.
A default task group may be specified to handle creation and termination
for tasks with no task group of their own.
In Ada 95, a task propagating an exception will silently be terminated.
This can be a significant hazard in high integrity systems.
An abstract type Root_Task_Group is declared as follows:
package Ada.Task_Groups is
type Root_Task_Group is abstract tagged limited private;
procedure Task_Created(TG : in out Root_Task_Group;
ID : Task_Identification.Task_ID);
procedure Unhandled_Exception(TG : in out Root_Task_Group;
ID : Task_Identification.Task_ID;
Excep : Exceptions.Exception_Occurrence) is abstract;
procedure Aborted(TG : in out Root_Task_Group;
ID : Task_Identification.Task_ID); --
procedure Normal_Termination(TG : in out Root_Task_Group;
ID : Task_Identification.Task_ID); --
procedure Never_Activated(TG : in out Root_Task_Group;
ID : Task_Identification.Task_ID); --
type Task_Group_Access is
access all Root_Task_Group'Class;
function Current_Task_Group return Task_Group_Access;
New_Default_Group : in Task_Group_Access;
Old_Default_Group : out Task_Group_Access);
function Current_Default_Task_Group return Task_Group_Access;
Group : access Root_Task_Group'Class) is
new Ada.Finalization.Limited_Controlled with private;
function Current_Task_Creation_Group return Task_Group_Access;
-- Return the current task creation group.
-- This is the task group established by the innermost
-- declaration of a Local_Task_Creation_Group, or the
-- current task's own group, if no local task creation
-- group has been established.
-- Returns null if the current task has
-- no task group and no local task creation group has
-- been established.
-- Note that the access value returned may designate
-- a local task group, and hence this value should
-- not be stored in a long-lived variable.
When a task is created, its task group is determined by the "task creation
group" associated with either its creator, if created by a declaration, or with
its access type, if created by an allocator. The environment task has no
associated task group, though it may establish a task creation group for its
The task creation group for a task starts out as the same as the task's own
group, if any. As the task proceeds, a local task creation group may be
established, by declaring a limited controlled object of the type
"Local_Task_Creation_Group." When the Local_Task_Creation_Group is finalized,
the task creation group is restored to its value at the time the local group
was established. The task creation group for an access type is determined by
the task creation group in effect when the access type declaration is
Immediately after a task is created, its task group, if any, is notified by
calling the Task_Created primitive of the task group. If the task does not have
a task group of its own, the default task group, if any, is notified. When the
Task_Created operation is called, a call on Current_Task will identify the
creator of the task, rather than the newly created task itself (since the new
task is generally not activated until later).
Immediately prior to task termination, the task group, if any, of the
terminating task is notified. If the task has no task group, then the default
task group at the time of termination, if any, is notified.
Notifying a task group of (impending) termination consists of calling the
appropriate primitive operation of the task group, as determined by how the
task terminates. If the task completes due to an abort statement, the Aborted
operation is called. If the task completes due to an unhandled exception, the
Unhandled_Exception operation is called. If the task completes due to
completing the last statement of the task body, or as part of waiting on a
terminate alternative, the Normal_Termination operation is called. In each
case, the ID parameter identifies the task that is terminating
For the Unhandled_Exception operation, the Excep parameter holds
the Exception_Occurrence which was not handled.
If a task never begins execution (because it is aborted prior to activation),
then the Never_Activated operation of its task group is called by its activator
at the point where it would have been activated (i.e. immediately
after the "begin"). It is unspecified whether some or all of its (unaborted)
"sibling" tasks are activated prior to Never_Activated being called.
The result of calling Current_Task while executing Never_Activated will
identify the activator. While executing the Normal_Termination and
Unhandled_Exception operations, the result of calling Current_Task identifies
the task about to terminate. It is not specified what is returned by
Current_Task within the Aborted operation.
When Unhandled_Exception, Aborted, or Normal_Termination are called,
the Terminated attribute of the terminating task will be False.
This means that task attributes created by an (active) instantiation of the
Ada.Task_Attributes package have not yet been finalized, allowing these
operations to read information from the attributes.
When Never_Activated is called, the Terminated attribute of the task will
be True, and the task attributes are not available.
When any of these operations is called, the Callable attribute of
the terminating task will be False.
Note that concurrent calls on task-group operations are possible due to
concurrent termination of tasks. Hence, these operations should be implemented
in a reentrant manner.
Note that Local_Task_Creation_Group may also be used in a library-level package
to set the task creation group for library-level tasks. The task creation
group setting will persist until it is overridden by some subsequent
elaboration of a Local_Task_Creation_Group object. Hence, if this approach is
used to manage library level tasks, such a Local_Task_Creation_Group object
should appear at the beginning of every package spec or body that declares
tasks or access types that designate tasks.
No_Task_Groups Restriction Identifier
The Restriction identifier "No_Task_Groups" is defined. When this restriction
is in force for a partition, it is illegal to mention the Task_Groups package
in a with clause of any compilation unit of the partition.
Many safety critical and high integrity systems prohibit exception handling,
and so the use of a "when others" handler at the task body level is then not
available. Furthermore, there may be many tasks in a system, and a systematic,
centralized way of handling unhandled exceptions is preferred to having to
repeat code in every task body.
We have proposed a technique that is reminiscent of what Java does, in that it
establishes a separate abstraction, called a task group ("thread group" in
Java) which receives notification of task termination. By default, a task
inherits the task group its master task. However, the master may establish a
"task creation group" to change what task group is used for its subtasks.
This proposal allows each scope to set up its own local task creation group,
but by default allows tasks to inherit the task group from its master task upon
creation. This gives a combination of fine control when needed, along with the
convenience of inheritance otherwise.
We ensure that a task cannot outlive its task group because local task creation
groups are specified using a declaration of a local controlled object, rather
than by an explicit subprogram call. For a task created by an allocator, we use
the task creation group the master had at the time the access type was
elaborated, again, to ensure that the task cannot outlive its task group
(barring early unchecked deallocation).
We provide a default task group to allow most or all of the tasks in a program
to notify a single task group, if appropriate.
We provide an operation for notifying a task group when a task is created, so
that it may keep track of all subtasks that exist, between their creation and
All of the primitive operations of the abstract Root_Task_Group type have
defaults except for Unhandled_Exception. Only that operation must be
overridden; the others do nothing if not overridden.
We originally did not have the notion of a task creation group, while instead
allowing a task to change its own group while it executed to indirectly affect
the group used for subtasks. This ended up having more complicated semantics,
and introduced additional possibilities for dangling references to task groups.
We originally had a Task_ID parameter in Current_Task_Group as a way to query
the task group of any task. However, this introduced too many possibilities for
dangling references, as a task might terminate immediately after the call and
its task group might go out of scope. An attribute of a task object might be an
alternative safer way of providing this functionality. By using an attribute,
we could avoid the need for using an access value, and could instead make the
attribute reference denote the task group itself, as a variable, analogous to
the way the Storage_Pool attribute works. On the other hand, if you can name
the task, then you can figure out its task group by simply calling
Current_Task_Creation_Group immediately prior to its declaration, and saving
the value in a local variable.
We considered a mechanism using pragmas or attributes to specify the task
creation group associated with a scope, but the mechanism became too heavy. The
Local_Task_Creation_Group controlled type provides much of the same capability
without having to define pragmas or attributes. On the other hand, the
semantics have been simplified a bit since the original proposal, and a pragma
equivalent to the Local_Task_Creation_Group declaration might be easier to
define now. It would also have the side benefit that it could deal with
library-level tasks somewhat more cleanly, as the pragma's effect could be
limited to the tasks of a single library package, without "spilling over" to
library packages that are elaborated immediately after the package. But this
would not be a pragma that could safely be ignored, as it has a significant
semantic effect over subsequent calls on functions like
package MTG is
type My_Task_Group is new Task_Groups.Root_Task_Group with ...
procedure Unhandled_Exception(TG : access My_Task_Group;
ID : Task_Identification.Task_ID;
Excep : Exceptions.Exception_Occurrence);
function Num_Unhandled(TG : access My_Task_Group) return Natural;
procedure Proc_With_Local_Tasks(...) is
Grp : aliased MTG.My_Task_Group; --
LTG : Task_Groups.Local_Task_Creation_Group(Grp'access);
task Local_Task1 is ...
task Local_Task2 is ...
if MTG.Num_Unhandled(Grp'access) > 0 then
[Editor's note: This originally was part of the Ravenscar proposal.
However, Ravenscar needed a simpler semantics, and the No_Task_Termination
restriction was defined to encapuslate the Ravenscar requirements.
At about the same time, the Exception Workshop at the 2001 Ada Europe meeting
identified a need for detection of task termination. This proposal was then
enhanced to meet those requirements.]
From: Randy Brukardt
Date: Wednesday, April 11, 2001, 5:41 PM.
This proposal seems to be exactly equivalent to the following code. (We use
code like this in Claw to handle locking, that the user can't lock
something and leave it locked by omission or by abnormal termination.)
Since this code is pure Ada 95, it would be useful to understand why this
is not an effective solution to the problem posed.
type Handler_Object is new Ada.Finalization.Limited_Controlled
with null record;
procedure Finalize (Object : in out Handler_Object) is
<handler_name>; -- Call to <handler_name>; as a practical matter,
-- you'd probably put the code here.
task My_Task ...
task body My_Task is
Termination_Handler : Handler_Object; -- Declare this first,
-- so it gets finalized last.
The basic idea is to insure that the last thing Finalized is your
termination handler. Clearly, it's easier to mess this up than with the
pragma (if someone sticks something in front of the termination object, you
could have trouble), but the semantics seem to be exactly the same. So I
would consider this proposal unnecessary without further justification. I'd
also suggest that the semantics be defined in terms of this sort of code if
possible (it would make the proposal look "simpler", which is always good).
From: Randy Brukardt
Date: Friday, March 8, 2002, 10:41 PM.
> Here is an update to AI 266 on task termination. I believe
> it incorporates most of the discussion from Cupertino.
Having read through my notes and the AI, I don't think it has added the
"Task_Created" (or whatever) operation that is mentioned in the notes
There also is a need for a No_Task_Group restriction.
There is some discussion of allocating a task group and it being a bounded
error. I don't know what this means off hand, so I can't tell if you've
ITRAW 11 proposes an alternative scheme, see AI-00266-02 for that.
Questions? Ask the ACAA Technical Agent