!standard D.2.2 (5) 02-03-07 AI95-00266/04 !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. 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 termination for tasks with no task group of their own. !problem In Ada 95, a task propagating an exception will silently be terminated. This can be 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 function Current_Task_Group return Task_Group_Access; -- Return the task group of the current task. -- This returns null if the current task has no -- associated task group. When a task with no task -- group terminates, the current default task -- group (see below) will be notified of termination. -- 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. 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 -- of termination of tasks with no task group of their own. -- 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_Creation_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 for the lifetime of the object -- a local task creation group for subtasks and -- for access types that designate tasks. -- The innermost local task creation group determines -- the task group when a subtask is created or -- an access type is elaborated. -- If no local task creation group has been established, -- the task's own group is used for its subtasks and -- access types. 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. private -- Not specified by the language end Ada.Task_Groups; 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 subtasks. 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. 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 elaborated. 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. !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. 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 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 Current_Task_Creation_Group. !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 creation group LTG : Task_Groups.Local_Task_Creation_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 creation 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. 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 begin ; -- Call to ; 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). **************************************************************** 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 several times. 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 handled it. ****************************************************************