Version 1.3 of ais/ai-00266.txt

Unformatted version of ais/ai-00266.txt version 1.3
Other versions for file ais/ai-00266.txt

!standard D.2.2 (5)          02-02-05 AI95-00266/03
!standard D.7 (00)
!class amendment 01-05-10
!status work item 01-05-10
!status received 01-05-10
!priority High
!difficulty Medium
!subject Task Termination procedure
!summary
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. A task inherits the task group of its master at the time of creation, but the task group may be changed during the task's lifetime.
!problem
In Ada 95, a task propagating an exception will silently be terminated. This is a significant hazard in high integrity systems.
!proposal
An abstract type Root_Task_Group is declared as follows:
with Ada.Task_Identification; with Ada.Exceptions; with Ada.Finalization; package Ada.Task_Groups is
type Root_Task_Group is abstract tagged limited private;
procedure Unhandled_Exception(TG : access Root_Task_Group; ID : Task_Identification.Task_ID; Excep : Exceptions.Exception_Occurrence) is abstract; -- Called immediately prior to task termination, -- if the task completed due to an unhandled exception. -- This operation must be overridden.
procedure Aborted(TG : access Root_Task_Group; ID : Task_Identification.Task_ID); -- is null; -- Called immediately prior to task termination, -- if the task completed due to abortion. -- By default, this operation does nothing.
procedure Normal_Termination(TG : access Root_Task_Group; ID : Task_Identification.Task_ID); -- is null; -- Called immediately prior to task termination, -- if the task completed due to completing the last statement -- of the task body, or as part of waiting on a terminate -- alternative. -- By default, this operation does nothing.
procedure Never_Activated(TG : access Root_Task_Group; ID : Task_Identification.Task_ID); -- is null; -- Called when a task is aborted before it -- is activated, by the would-be activator -- at the normal point of activation (immediately -- after the "begin"). -- By default, this operation does nothing.
type Task_Group_Access is access all Root_Task_Group'Class; -- Reference to task group object
procedure Specify_Task_Group( New_Group : in Task_Group_Access; Old_Group : out Task_Group_Access; ID : in Task_Identification.Task_ID := Task_Identification.Current_Task); -- Specify the task group for the identified task. -- Its current task group is returned in Old_Group.
function Current_Task_Group( ID : in Task_Identification.Task_ID := Task_Identification.Current_Task) return Task_Group_Access; -- Return the current task group of the identified task. -- If this returns null at the time of termination, then -- the current default task group (see below) will be -- notified of termination.
procedure Specify_Default_Task_Group( New_Default_Group : in Task_Group_Access; Old_Default_Group : out Task_Group_Access); -- Specify the task group to receive notifications -- from tasks that terminate when they have a null task group. -- The current "default" task group is returned in -- Old_Default_Group, and is initially null.
function Current_Default_Task_Group return Task_Group_Access; -- Return the current default task group
type Local_Task_Group( Group : access Root_Task_Group'Class) is new Ada.Finalization.Limited_Controlled with private; -- Declaring an object of this type has the effect -- of establishing a local task group for current task; -- Upon finalization, the original task group is restored.
private -- Not specified by the language end Ada.Task_Groups;
Upon termination, the task group for a task is notified. The task group for a task is determined as follows:
a) The task group for the environment task is initially null;
b) The task group for a task created by a declaration
is set to that of its master task at the time of creation;
c) The task group for a task created by an allocator is set
to the group of its master task at the time of the elaboration of the ultimate ancestor of the access type.
d) The task group for a task can be set (or reset) by a call on
Specify_Task_Group identifying the task, or (for the current task) by elaborating or finalizing an object of type Local_Task_Group.
e) Upon termination, if the task group is still null, then
the current "default" task group is used, as specified by the most recent call on "Specify_Default_Task_Group."
In other words, a task typically inherits the task group of its master upon creation, can have its group (re)set explicitly during its lifetime, and defaults to the "default task group" upon termination.
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, and the Terminated attribute of the task will be True. For the other three operations, the result of calling Current_Task while executing one of the task-group operations identifies the task about to terminate, and the Terminated attribute of the task will be False. In either case, the Callable attribute of the task will be False.
When Unhandled_Exception, Aborted, or Normal_Termination are called, 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. Inside Never_Activated, the task attributes are not available.
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.
To support local scopes where a distinct task group is to be used, we have provided a limited controlled type, called "Local_Task_Group," whose objects establish a specified task group when elaborated, and restore the old task group when finalized. We provide this mechanism because it is safer than explicitly calling Specify_Task_Group before and after the scope, as it works even if the local scope propagates an exception or is aborted, and works safely with stack-allocated task groups.
Note that Local_Task_Group may also be used in a library-level package to set the task group for library-level tasks. The task group setting will persist until it is overridden by some subsequent elaboration of a Local_Task_Group object, or an explicit call on Specify_Task_Group for the environment task. Hence, if this approach is used to manage library level tasks, such a Local_Task_Group object should appear at the beginning of every package spec or body that declares tasks.
!wording
!discussion
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. However, this proposal allows a task to change its task group after activation. A task inherits the task group of its master task whenever its master task's group is non-null and the task's group is null.
This proposal allows each task to have its own task group, but also 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 only inherit the task group from the master at the point of creation, so there is less chance of a task outliving its task group. For a task created by an allocator, we use the task group the master had at the time the access type was elaborated, again, to minimize the chance of having the task outlive its task group.
We allow a task to change its task group during execution so that an inner scope can establish a local task group, wait for all local tasks to terminate, and then restore the old task group. The Local_Task_Group controlled type provides this capability in a way that minimizes the chance of dangling references.
We provide a default task group to allow most or all of the tasks in a program to share a single group, if appropriate.
Because task terminations can occur immediately after task activation, it is We considered having two task groups associated with a given task, one for the task itself to notify of its termination, and one to be the task group to be used for subtasks created that have this task as a master. This would simplify things in one way, since the group to use for the task itself would not likely change during its lifetime. However, the group to use for new subtasks would still need to be saved and restored around local scopes, and by the time the task terminated, the task's own group and the group for subtasks would likely end up being the same again. Hence, we felt it was overall simpler to use a single group for both purposes. [This decision might deserve revisiting, since it does introduce some possibility of dangling references as a result of calling Current_Task_Group on a task that has temporarily changed its task group to be a local task group. Perhaps Current_Task_Group should not take a task ID, and Specify_Task_Group should not return the old group (given that the Local_Task_Group object does an automatic save/restore). Alternatively, we would have two, but Local_Task_Group would be the only way to affect the "subtask creation group" while, Specify_Task_Group and Current_Task_Group would refer to the task's "own" group.]
We considered a mechanism using pragmas or attributes to specify the task group associated with a scope, but the mechanism became too heavy. The Local_Task_Group controlled type provides much of the same capability without having to define pragmas or attributes.
!example
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); -- Handle notifications of unhandled exceptions
-- other operations of task group not overridden
function Num_Unhandled(TG : access My_Task_Group) return Natural; -- Return count of how many tasks died due to unhandled exceptions end MTG;
with MTG; procedure Proc_With_Local_Tasks(...) is Grp : aliased MTG.My_Task_Group; -- Local task group object begin declare -- Establish local task group LTG : Task_Groups.Local_Task_Group(Grp'access);
-- Declare some local tasks task Local_Task1 is ... task Local_Task2 is ... begin ... -- if Local_Task1 or Local_Task2 dies due to an unhandled excep -- will call Unhandled_Exception(Grp, ...)
end; -- old task group restored implicitly at this point
if MTG.Num_Unhandled(Grp'access) > 0 then ... -- we got problems, report them end if;
end Proc_With_Local_Tasks;
!ACATS test
!appendix

[Editor's note: This originally was part of the Ravenscar proposal,
see AI-249.]

****************************************************************

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
     begin
         <handler_name>; -- Call to <handler_name>; as a practical matter,
             -- you'd probably put the code here.
     end Finalize;

     task My_Task ...

     task body My_Task is
         Termination_Handler : Handler_Object; -- Declare this first,
               -- so it gets finalized last.
          ...
     end My_Task;

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).

****************************************************************

Questions? Ask the ACAA Technical Agent