Rationale for Ada 2005
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.
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: