Rationale for Ada 2005

John Barnes
Table of Contents   Index   References   Search   Previous   Next 

5.2 Task termination

In the Introduction we mentioned the problem of how tasks can have a silent death in Ada 95. This happens if a task raises an exception which is not handled by the task itself. Tasks may also terminate because of going abnormal as well as terminating normally. The detection of task termination and its causes can be monitored in Ada 2005 by the package Ada.Task_Termination whose specification is essentially
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Exceptions; use Ada.Exceptions;
package Ada.Task_Termination is
   pragma Preelaborable(Task_Termination);
   type Cause_Of_Termination is (Normal, Abnormal, Unhandled_Exception);
   type Termination_Handler is access protected
         procedure(Cause: in Cause_Of_Termination;
                         T: in Task_Id; X: in Exception_Occurrence);
   procedure Set_Dependents_Fallback_Handler (Handler: in Termination_Handler);
   function Current_Task_Fallback_Handler return Termination_Handler;
   procedure Set_Specific_Handler(T: in Task_Id; Handler: in Termination_Handler);
   function Specific_Handler(T: in Task_Id) return Termination_Handler;
end Ada.Task_Termination;
(Note that the above includes use clauses in order to simplify the presentation; the actual package does not have use clauses. We will use a similar approach for the other predefined packages described in this chapter.)
The general idea is that we can associate a protected procedure with a task. The protected procedure is then invoked when the task terminates with an indication of the reason passed via its parameters. The protected procedure is identified by using the type Termination_Handler which is an access type referring to a protected procedure.
The association can be done in two ways. Thus (as in the Introduction) we might declare a protected object Grim_Reaper 
protected Grim_Reaper is
   procedure Last_Gasp(C: Cause_Of_Termination; T: Task_Id; X: Exception_Occurrence);
end Grim_Reaper;
which contains the protected procedure Last_Gasp. Note that the parameters of Last_Gasp match those of the access type Termination_Handler.
We can then nominate Last_Gasp as the protected procedure to be called when the specific task T dies by 
Set_Specific_Handler(T'Identity, Grim_Reaper.Last_Gasp'Access);
Alternatively we can nominate Last_Gasp as the protected procedure to be called when any of the tasks dependent on the current task becomes terminated by writing 
Set_Dependents_Fallback_Handler(Grim_Reaper.Last_Gasp'Access);
Note that a task is not dependent upon itself and so this does not set a handler for the current task.
Thus a task can have two handlers. A fallback handler and a specific handler and either or both of these can be null. When a task terminates (that is after any finalization but just before it vanishes), the specific handler is invoked if it is not null. If the specific handler is null, then the fallback handler is invoked unless it too is null. If both are null then no handler is invoked.
The body of protected procedure Last_Gasp might then output various diagnostic messages to a log for later analysis, thus 
procedure Last_Gasp(C: Cause_Of_Termination; T: Task_Id; X: Exception_Occurrence) is
begin
   case C is
      when Normal => null;
      when Abnormal =>
         Put_Log("Something nasty happened to task ");
         Put_Log(Image(T));
      when Unhandled_Exception =>
         Put_Log("Unhandled exception occurred in task ");
         Put_Log(Image(T));
         Put_Log(Exception_Information(X));
   end case;
end Last_Gasp;
There are three possible reasons for termination, it could be normal, abnormal (caused by abort), or because of propagation of an unhandled exception. In the last case the parameter X gives details of the exception occurrence whereas in the other cases X has the value Null_Occurrence.
Initially both specific and fallback handlers are null for all tasks. However, note that if a fallback handler has been set for all dependent tasks of T then the handler will also apply to any task subsequently created by T or one of its descendants. Thus a task can be born with a fallback handler already in place.
If a new handler is set then it replaces any existing handler of the appropriate kind. Calling either setting procedure with null for the handler naturally sets the appropriate handler to null.
The current handlers can be found by calling the functions Current_Task_Fallback_Handler or Specific_Handler; they return null if the handler is null.
It is important to realise that the fallback handlers for the tasks dependent on T need not all be the same since one of the dependent tasks of T might set a different handler for its own dependent tasks. Thus the fallback handlers for a tree of tasks can be different in various subtrees. This structure is reflected by the fact that the determination of the current fallback handler of a task is in fact done by searching recursively the tasks on which it depends.
Note that we cannot directly interrogate the fallback handler of a specific task but only that of the current task. Moreover, if a task sets a fallback handler for its dependents and then enquires of its own fallback handler it will not in general get the same answer because it is not one of its own dependents.
It is important to understand the situation regarding the environment task. This unnamed task is the task that elaborates the library units and then calls the main subprogram. Remember that library tasks (that is tasks declared at library level) are activated by the environment task before it calls the main subprogram.
Suppose the main subprogram calls the setting procedures as follows 
procedure Main is
   protected RIP is
      procedure One( ... );
      procedure Two( ... );
   end;
   ...
begin
   Set_Dependents_Fallback_Handler(RIP.One'Access);
   Set_Specific_Handler(Current_Task, RIP.Two'Access);
   ...
end Main;
The specific handler for the environment task is then set to Two (because Current_Task is the environment task at this point) but the fallback handler for the environment task is null. On the other hand the fallback handler for all other tasks in the program including any library tasks is set to One. Note that it is not possible to set the fallback handler for the environment task.
The astute reader will note that there is actually a race condition here since a library task might have terminated before the handler gets set. We could overcome this by setting the handler as part of the elaboration code thus 
package Start_Up is
   pragma Elaborate_Body;
end;
with Ada.Task_Termination; use Ada.Task_Termination;
package body Start_Up is
begin
   Set_Dependents_Fallback_Handler(RIP.One'Access);
end Start_Up;
with Start_Up;
pragma Elaborate(Start_Up);
package Library_Tasks is
   ...    -- declare library tasks here
end;
Note how the use of pragmas Elaborate_Body and Elaborate ensures that things get done in the correct order.
Some minor points are that if we try to set the specific handler for a task that has already terminated then Tasking_Error is raised. And if we try to set the specific handler for the null task, that is call Set_Specific_Handler with parameter T equal to Null_Task_Id, then Program_Error is raised. These exceptions are also raised by calls of the function Specific_Handler in similar circumstances.

Table of Contents   Index   References   Search   Previous   Next 
© 2005, 2006 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe