!standard D.2.2 (5) 03-08-17 AI95-00266-02/04
!standard D.7 (00)
!class amendment 01-06-01
!status work item 01-06-01
!status received 01-06-01
!priority High
!difficulty Medium
!subject Task termination procedure
!summary
A mechanism is proposed for associating a protected procedure
with a task. This procedure is invoked when the task is about to terminate
(either normally, as a result of an unhandled exception or due to abort).
If a task terminates due to an unhandled exception, the exception occurrence
is passed as a parameter to the procedure. The proposal provides an alternative
to that of AI95-00266-01 which was rejected by IRTAW11; it responds to earlier
comments by the ARG. The proposal here introduces default handlers for task
hierarchies, but does not go as far as task groups.
!problem
In Ada 95, a task propagating an exception will be silently terminated.
This can be a significant hazard in high integrity systems.
!proposal
A package for handling task termination is defined as follows.
with Ada.Task_Identification; use Task_Identification;
with Ada.Exceptions; use Ada.Exceptions;
package Ada.Task_Termination is
type Cause is (Normal, Abnormal, Unhandled_Exception);
type Handler is access protected procedure(
C: Cause; Id: Task_Id; X: Exception_Occurrence := Null_Id);
-- Null_Id is used when cause is not Unhandler_Exception
procedure Set_Dependents_Default_Handler(New_Handler: in Handler);
-- Sets the default handler for all task dependents.
-- Handlers are not set on blocks, only on tasks
--
-- New_Handler is called immediately prior to dependent 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 then C is set to Normal and no exception id
-- is given. If termination is due to abort then C is set
-- to Abnormal and no exception id is given. If termination is
-- due to a unhandled exception then C is set to Unhandled_
-- Exception and the associated exception id is set.
procedure Get_Handler(Old_Handler: out Handler);
-- Returns the previously set handler, or default or null.
procedure Set_Handler(Id : Task_Id; New_Handler: in Handler;
Old_Handler : out Handler);
-- A handler is set for the task; overriding any default. Old_Handler
-- returns any previously set handler, or default handler or null.
-- If a null handler is set then thsi re-estabilishes the default.
end Ada.Task_Termination;
!wording
!discussion
Many safety critical and high integrity systems prohibit (or discourage)
exception handling, and so the use of a "when others" handler at the
outer most level of 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. The proposed solution is applicable
at the general language level but it is also appropriate for a
Ravenscar environment.
The primary goal of this proposal is to address only the issue of silent
termination. Consequently, the proposal does not address issues of
termination as a result of failures after task creation but before task
activation. It is not clear how useful detection of failure after creation
but before activation is, as the declarative region in which the task is
being created could fail before the creation point. Hence, the order of
elaboration of the declarative region containing the task would dictate
whether or not the event would be detected. Note also, failure of task
before execution in a Ravenscar environment would cause the program to fail
and the problem would need to be handled at a different level.
This proposal allows defaults (for the active partition) to be assigned
for all tasks; or per-task handlers to be set. It does not attempt to
define task groups. But a default is for all dependent tasks. Hence setting
the default for the environment task will cover all tasks in the partition.
With a task hierarchy two programming styles are possible. The first
involves replacing the default for a part of the tree - this is directly
supported by the proposal. The second involves adding to the existing
default - this can be programmed by first getting the original default
and then creating a new handler that incorporates the new functionality
plus a call to the original handler. Note this would be much easier to
program with an extendable protected type!
When a call on Set_Handler is made and the task
has already terminated then the handler is executed immediately.
If a null default handler is set then this is equivalent to there being
no handler.
The requirement is that the user-supplied protected subprogram is usually
called after the task has been finalized but on the stack of the terminating
task. Consequently, if Task_Identification.Current_Task is called from the
handler, the terminating task identifier is returned.
If the task has already terminated, the handler is called on the stack
of the calling task, and Task_Identification.Current_Task
returns the calling task.
It is a bounded error for the user-supplied protected subprogram to
propagate an exception.
!example
The following example illustrates how the mechanisms can be used.
The example is of a library package that logs the termination of all tasks and,
separately, the terminations due to unhandled exceptions. It also releases
a guardian task if any task fails due to being aborted. At the end of
the program (when the environment task wishes to terminate) the logged
information is output. This example assumes no tasks are declared within
library units (or that elaboration control is used to make sure the
code of the package body is executed before any library tasks start
executing).
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Task_Termination; use Ada.Task_Termination;
with Ada.Exceptions; use Ada.Exceptions;
package Termination_Logging is -- library package
protected Logger is
entry Guardian_Control;
procedure Log_Termination(C : Cause;
Id: Task_Id; X: Exception_Occurrence := Null_Id);
procedure Output(C : Cause;
Id: Task_Id; X: Exception_Occurrence := Null_Id);
private
Abort_Occurrence : Boolean := False;
Finished : Natural := 0;
Exception_Finished : Natural := 0;
end Logger;
task Guardian; -- calls Logger.Guardian_Control
end Termination_Logging;
package body Termination_Logging is
protected body Logger is
entry Guardian_Control when Abort_Occurrence is
begin
Abort_Occurrence := False;
end Guardian_Control;
procedure Log_Termination (C : Cause;
Id: Task_Id; X: Exception_Occurrence := Null_Id) is
begin
Finished := Finished + 1;
if Cause = Unhandled_Exception then
Exception_Finished := Exception_Finished + 1;
elsif Cause = Abnormal then
Abort_Occurrence := True;
end if;
end Log_Termination;
procedure Output(C : Cause;
Id: Task_Id; X: Exception_Occurrence := Null_Id) is
begin
-- print out the values of Finished and Exception_Finished
end Output;
task body Guardian is ...
Temp : Handler;
end Logger;
begin
Set_Default_Handler(Logger.Log_Termination'Access);
Set_Handler(Current_Task, Logger.Output'Access, Temp);
end Termination_Logging;
!discussion
Would it be useful to have a pragma to request the use of these
facilities?
!ACATS test
!appendix
!standard D.2.2 (5) 02-04-12 AI95-???
!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
!from A. Burns and A.J. Wellings on behalf of IRTAW11
!summary
A mechanism is proposed for associating procedures or protected procedures
with a task. These procedures are invoked when the task is about to terminate
(either normally, as a result of an unhandled exception or due to abortion).
If a task terminates due to an unhandled exception, the exception occurrence
is passed as a parameter to the procedure. The proposal provides an alternative
to that of AI95-00266 which was rejected by IRTAW11. The reasons for the
rejection are summarised by the rapporteur's report which is attached
as an appendix to this AI. Whilst there might be some discussion on
the validity of these reasons (see appendix), in summary, the overall
feeling at the workshop was that the proposal was a "sledge hammer
to crack a nut".
!problem
In Ada 95, a task propagating an exception will be silently terminated.
This can be a significant hazard in high integrity systems.
!proposal
A package for handling task termination is defined as follows.
with Ada.Task_Identification; use Task_Identification;
with Ada.Exceptions; use Ada.Exceptions;
package Ada.Task_Termination is
type Termination_Handler is access procedure (
Id: Task_Id);
type Exceptional_Termination_Handler is access procedure (
Id: Task_Id; X: Exception_Occurrence);
procedure Set_Normal_Termination_Handler(
Handler : Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is 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.
procedure Set_Unhandled_Exception_Handler(
Handler: Exceptional_Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is called immediately prior to task termination,
-- if the task completed due to an unhandled exception during
-- execution or activation.
procedure Set_Abnormal_Termination_Handler(
Handler: Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is called immediately prior to task termination,
-- if the task completed due to abortion.
type Protected_Termination_Handler is access protected procedure(
Id: Task_Id);
type Protected_Exceptional_Termination_Handler is access protected procedure(
Id: Task_Id; X: Exception_Occurrence);
procedure Set_Normal_Termination_Protected_Handler(
Handler: Protected_Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is 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.
procedure Set_Unhandled_Exception_Protected_Handler(
Handler: Protected_Exceptional_Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is called immediately prior to task termination,
-- if the task completed due to an unhandled exception
-- during execution or activation
procedure Set_Abnormal_Termination_Protected_Handler(
Handler: Protected_Termination_Handler;
Id : Task_Id := Current_Task);
-- The Handler is called immediately prior to task termination,
-- if the task completed due to abortion.
end Ada.Task_Termination;
!wording
!discussion
Many safety critical and high integrity systems prohibit (or discourage)
exception handling, and so the use of a "when others" handler at the
outer most level of 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. The proposed solution is applicable
at the general language level but it is also appropriate for a
Ravenscar environment.
The package could be a child of Ada.Task_Identification.
The primary goal of this proposal is to address only the issue of silent
termination. Consequently, the proposal does not address issues of
termination as a result of failures after task creation but before task
activation. It is not clear how useful detection of failure after creation
but before activation is, as the declarative region in which the task is
being created could fail before the creation point. Hence, the order of
elaboration of the declarative region containing the task would dictate
whether or not the event would be detected. Note also, failure of task
before execution in a Ravenscar environment would cause the program to fail
and the problem would need to be handled at a different level.
When any of the (protected) subprograms are called and the task
has already terminated then there are three possible semantics to consider:
a) view as a null operation,
b) raise an exception,
c) call the handler immediately
Option a) would seem inappropriate, as the task will have then died silently.
Option b) may not be acceptable in a no-exception environment.
Option (c) avoids any potential race conditions.
The user-supplied procedure, in this case, is called from the task
which executed the associated subprogram.
The requirement is that the user-supplied (protected) subprogram is usually called
after the task has been finalized but on the stack of the terminating task.
Consequently, if Task_Identification.Current_Task is called from one of the
subprograms, the terminating task identifier is returned.
If the task has already terminated, the handler is called on the stack
of the calling task, and Task_Identification.Current_Task
returns the calling task.
It is a bounded error for the user-supplied (protected) subprogram to
propagate an exception.
A model that allows handlers to be attached and detached in a
similar manner to that provided by Ada.Interrupts was considered
but rejected for simplicity of implementation. In the current proposal,
the setting of a handler over-writes any previous handler.
An alternative model was considered whereby the following attributes were
defined -- where T is a task type (after any implicit dereference):
T'Unhandled_Exception_Handler
T'Abnormal_Termination_Handler
T'Normal_Termination_Handler
T'Activation_Failure_Handler
T'Termination_Without_Activation_Handler
These would return subprograms similar to that defined above.
They can be set via attribute_definition_clauses. This gives more
functionality than the selected approach (in particular it allows failures
before task activation to be handled). However, it has more impact on
the language definition. The approach was rejected because a) of the
language impact, b) the assumed target market here is a Ravenscar
environment and failures before the tasks begin their execution would
cause the program to fail, and c) the added functionality is of questionable
use.
Another drawback of the attribute approach is that it doesn't scale up to
applications where tasks are allocated dynamically (at least not if the
program needs to associate a different termination hook with different
dynamically allocated tasks of the same task type).
A pragma-based approach has the same set of issues as the attribute
approach.
!example
The following two examples illustrate how the mechanisms can be used.
Example 1
The first example is of a package that logs the failures of tasks due to
unhandled exceptions.
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Exceptions; use Ada.Exceptions;
package Termination_Logging is
procedure Log_Non_Normal_Termination (
Id: Task_Id; X: Exception_Occurrence);
end Termination_Logging;
package body Termination_Logging is
procedure Log_Non_Normal_Termination (
Id: Task_Id; X: Exception_Occurrence) is
begin
-- write out error message to operator terminal
-- log event to file
end Log_Non_Normal_Termination;
end Termination_Logging;
A program fragment using this package is as follows:
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Task_Termination; use Ada.Task_Termination;
with Termination_Logging; use Termination_Logging;
package body Ravenscar_Example is
task MyTask;
task body MyTask is
begin
Ada.Termination.Set_Unhandled_Exception_Handler(
Log_Non_Normal_Termination'Access);
loop
. . .;
end loop;
end MyTask;
end Ravenscar_Example;
Example 2:
The second example illustrate how a task can wait for the normal or
abnormal termination of a task and be able to detect the difference.
First consider a package which provides a synchronization agent.
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Termination; use Ada.Termination;
with Ada.Exceptions; use Ada.Exceptions;
package Signals is
protected Signal is
procedure Normal_Termination(Tid : Task_Id);
procedure Abnormal_Termination(Tid: Task_Id);
procedure Exceptional_Termination(Tid : Task_Id;
Excep : Exception_Occurrence);
entry Wait_Termination(Normal: out Boolean);
private
Signal_Arrived : Boolean := False;
Normal : Boolean;
end Signal;
end Signals;
Whichever subprogram is called will open the barrier associated with
the entry and set the Boolean out parameter accordingly.
The package can be used as follows:
with Signals; use Signals;
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Task_Termination; use Ada.Task_Termination;
procedure Another_Example is
task MyTask;
task body MyTask is
begin
. . .;
end MyTask;
Normal : Boolean;
begin
Set_Abnormal_Termination_Protected_Handler(
Signal.Abnormal_Termination'Access,
MyTask'Identity);
-- similarly for normal termination, and exceptional termination;
Signal.Wait_Termination(Normal);
if(Normal) then . . .;
end Another_Example;
!ACATS test
!appendix
At the 11th International Real-Time Ada Workshop, AI95-00266 was discussed.
The following is taken from Ben Brosgol's rapporteur's report.
3.2 AI-266: Task termination procedure
This AI proposes a mechanism ("task group") through which the application
programmer can provide "termination hooks" for procedures that are called
under a variety of circumstances related to task termination (unhandled
exception, abort, normal termination, never activated). The task group
concept is based on the "thread group" notion in Java. The AI stems from a
discussion at the Exceptions workshop at Ada Europe 2001.
Several issues were discussed:
(1) Is "silent task death" a problem that needs to be solved?
(2) Is anything special needed (i.e., can controlled types be used)?
(3) Is the Java thread group an appropriate basis for the design?
Regarding (1), there was general support for the need for such functionality
(e.g. for fault tolerance or "health monitoring"); the vote was 13-0. Note:
this vote applied only to the unhandled exception hook, not to the
additional items (abort etc.), although subsequent discussion showed support
for these also.
Regarding (2), controlled types alone are not sufficient. Finalization for
a controlled object occurs when the object is destroyed, but (e.g. in
Ravenscar) such destruction will only take place when the object is
unchecked-deallocated, and unchecked deallocation might be disallowed by the
implementation or by application requirements. Moreover the "hook" needs to
be invoked not when the object is finalized, but at the earlier point when
the task terminates.
Regarding (3), there was some concern that the design was based on a Java
feature which seems to have fallen into disrepute in Java. Bloch's book
"Effective Java" states: "... thread groups are largely obsolete.... [they]
don't provide much in the way of useful functionality, and much of the
functionality they do provide is flawed. Thread groups are best viewed as
an unsuccessful experiment, and you may simply ignore their existence.... If
you are designing a class that deals with logical groups of threads, just
store the Thread references comprising each logical group in an array or
collection." Bloch goes on to observe: "There is a minor exception to the
advice that you should simply ignore thread groups. One small piece of
functionality is available only in the ThreadGroup API. The
ThreadGroup.uncaughtException method is automatically invoked when a thread
in the group throws an uncaught exception." The AI would need a much
stronger rationale for the introduction of a ThreadGroup-based mechanism,
given this negative experience from Java.
The vote on the specific proposal in the AI was 0-16; it was felt that the
mechanism was complex and beyond the needed functionality, and that it was
stylistically clumsy to use if all you wanted to do was to provide an
unhandled exception procedure for a specific task. Several alternative
approaches were conjectured: special task attribute procedures, and a
proposal based on task-ids.
An example of a possible approach using attributes:
task T; -- also OK for task type
procedure My_Unhandled_Exception_Procedure(Id : Task_Id; E :
Exception_Occurrence);
for T'Unhandled_Exception_Hook use My_Unhandled_Exception_Procedure;
A drawback of using special attributes is that it affects the compiler;
however, the original AI might also affect the compiler in terms of the need
for calls to be inserted at special places. Another drawback of the
attribute approach is that it doesn't scale up to applications where tasks
are allocated dynamically (at least not if the program needs to associate a
different termination hook with different dynamically allocated tasks of the
same task type).
A pragma-based approach has the same set of issues as the attribute
approach.
The AI uses OOP and provides a flexible and extensible framework, whereas
the attribute and task id approaches are somewhat more special purpose.
However, OOP may be a disadvantage in some communities, particularly the
High Integrity domain, so the use of OOP is a double-edged sword.
There was agreement that whatever mechanism was adopted should be applicable
both to the full language and to restricted environments such as Ravenscar.
Subsequent Email exchanges
--------------------------
From: Brian Dobbing
Ben,
Thanks for the copy of the write-up. I have a few observations:
> 3.2 AI-266: Task termination procedure
> Several issues were discussed:
> (1) Is "silent task death" a problem that needs to be solved?
> (2) Is anything special needed (i.e., can controlled types be used)?
> (3) Is the Java thread group an appropriate basis for the design?
> Regarding (2), controlled types alone are not sufficient. Finalization
for
> a controlled object occurs when the object is destroyed, but (e.g. in
> Ravenscar) such destruction will only take place when the object is
> unchecked-deallocated, and unchecked deallocation might be disallowed by
the
> implementation or by application requirements. Moreover the "hook" needs
to
> be invoked not when the object is finalized, but at the earlier point when
> the task terminates.
This argument is invalid. The controlled types solution is based on
declaring
a controlled object *inside* the task body declarative part so that when
the task terminates (by whatever reason) it executes the Finalize routine
of its local controlled object. This works fine in implementation terms.
The counter-argument should be as follows:
(a) The handling of task termination is required in programs that execute
with a restricted run-time system, such as Ravenscar, in which the use of
local controlled objects may be prohibited via the No_Nested_Finalization
restriction.
(b) The controlled object solution relies on the finalization of the object
being the last action of the task. If further controlled objects were
inserted (say during maintenance) earlier in the declarative part, these
would not be finalized prior to the "termination handler" Finalize routine
which may give rise to unpredictable effects.
(c) The controlled object solution is somewhat kludgey and obscures the
readability of the true intent (and hence (b) is more likely to occur).
The handling of task termination is considered sufficiently important by the
real time and high integrity communities as to warrant more "first class"
explicit treatment.
> Regarding (3), there was some concern that the design was based on a Java
> feature which seems to have fallen into disrepute in Java....
This argument is also invalid. It is generally accepted that the *only*
useful use of thread groups in Java is for thread termination, especially
via uncaught exceptions. The Java execution model is therefore likely to
continue to use thread groups in this way. Having an Ada model of task
termination / unhandled exceptions that is compatible with Java's should
simplify mixed-language systems, e.g. running Ada with RT-Java.
> The AI uses OOP and provides a flexible and extensible framework, whereas
> the attribute and task id approaches are somewhat more special purpose.
> However, OOP may be a disadvantage in some communities, particularly the
> High Integrity domain, so the use of OOP is a double-edged sword.
This is also becoming an invalid argument. The use of OOP in real-time and
even high integrity systems is emerging, given certain restrictions. For
example, at Praxis we are currently adding support for a subset of Ada's
OOP features (no class-wide programming, but everything else in) into the
SPARK Examiner because of market pressure. Also the High Integrity Profile
for Java that I prepared for the J Consortium of course supports OOP (it
wouldn't be Java at all if it didn't). I have been to several presentations
of critical real-time systems that are using the OOP features of Ada.
IMHO, I think that the group needs stronger arguments than this to show
that the current ARG solution should be rejected.
-- Brian
>From Ben Brosgol:
> Ben,
>
> Thanks for the copy of the write-up. I have a few observations:
>
> > 3.2 AI-266: Task termination procedure
>
> > Several issues were discussed:
> > (1) Is "silent task death" a problem that needs to be solved?
> > (2) Is anything special needed (i.e., can controlled types be used)?
> > (3) Is the Java thread group an appropriate basis for the design?
>
> > Regarding (2), controlled types alone are not sufficient. Finalization
> for
> > a controlled object occurs when the object is destroyed, but (e.g. in
> > Ravenscar) such destruction will only take place when the object is
> > unchecked-deallocated, and unchecked deallocation might be disallowed by
> the
> > implementation or by application requirements. Moreover the "hook"
needs
> to
> > be invoked not when the object is finalized, but at the earlier point
when
> > the task terminates.
>
> This argument is invalid. The controlled types solution is based on
> declaring
> a controlled object *inside* the task body declarative part so that when
> the task terminates (by whatever reason) it executes the Finalize routine
> of its local controlled object. This works fine in implementation terms.
OK, I was thinking that it was the reverse (declare a task inside a
controlled object). Nonetheless, your counter-arguments are pretty
convincing :-)
> The counter-argument should be as follows:
>
> (a) The handling of task termination is required in programs that execute
> with a restricted run-time system, such as Ravenscar, in which the use of
> local controlled objects may be prohibited via the No_Nested_Finalization
> restriction.
>
> (b) The controlled object solution relies on the finalization of the
object
> being the last action of the task. If further controlled objects were
> inserted (say during maintenance) earlier in the declarative part, these
> would not be finalized prior to the "termination handler" Finalize routine
> which may give rise to unpredictable effects.
>
> (c) The controlled object solution is somewhat kludgey and obscures the
> readability of the true intent (and hence (b) is more likely to occur).
> The handling of task termination is considered sufficiently important by
the
> real time and high integrity communities as to warrant more "first class"
> explicit treatment.
>
> > Regarding (3), there was some concern that the design was based on a
Java
> > feature which seems to have fallen into disrepute in Java....
>
> This argument is also invalid. It is generally accepted that the *only*
> useful use of thread groups in Java is for thread termination, especially
> via uncaught exceptions. The Java execution model is therefore likely to
> continue to use thread groups in this way. Having an Ada model of task
> termination / unhandled exceptions that is compatible with Java's should
> simplify mixed-language systems, e.g. running Ada with RT-Java.
I disagree. The task group / thread group machinery looks like a very heavy
way to achieve a conceptually simple effect, and I don't find the
mixed-language argument convincing. I know the style required in Java to
define an uncaughtException method, and it is rather clumsy. At best,
consistency with Java thread groups is a "nice to have" aspect; at worst, it
will have people wondering why we chose to include a feature that was, in
the words of Josh Bloch (a respected Java expert), a "failed experiment".
> > The AI uses OOP and provides a flexible and extensible framework,
whereas
> > the attribute and task id approaches are somewhat more special purpose.
> > However, OOP may be a disadvantage in some communities, particularly the
> > High Integrity domain, so the use of OOP is a double-edged sword.
>
> This is also becoming an invalid argument. The use of OOP in real-time and
> even high integrity systems is emerging, given certain restrictions. For
> example, at Praxis we are currently adding support for a subset of Ada's
> OOP features (no class-wide programming, but everything else in) into the
> SPARK Examiner because of market pressure. Also the High Integrity Profile
> for Java that I prepared for the J Consortium of course supports OOP (it
> wouldn't be Java at all if it didn't). I have been to several
presentations
> of critical real-time systems that are using the OOP features of Ada.
I'm pleased to see that OOP is making inroads, but I don't see an advantage
in a solution that requires OOP when there are technically acceptable
solutions that do not. Are there potential Ada real-time users who would be
attracted more by an OOP than a non-OOP solution? Perhaps, but I am
skeptical. Are there potential Ada real-time users who would be attracted
more by a non-OOP than an OOP solution? At present, yes.
> IMHO, I think that the group needs stronger arguments than this to show
> that the current ARG solution should be rejected.
This sounds like very strange reasoning. We were given a proposal to
review, there was no one at the IRTAW who was a champion for the AI, and we
provided our reaction. The negative vote was not even close. If the ARG
was trying to get the IRTAW to support the AI, then they didn't quite
achieve this goal :-)
As you have observed, Andy is in the process of preparing an alternative
proposal for task termination hooks. IMHO the way to proceed will be to
have a technical analysis of the two approaches (including examples of
typical usages, analysis of race condition avoidance, etc.) and then make an
appropriate judgement. (The "IMHO" needs to be taken into account. I am
speaking here for myself; perhaps Joyce would like to formulate an official
response on behalf of IRTAW.)
> -- Brian
-Ben
From: Brian Dobbing
Ben,
You wrote:
> at worst, it
> will have people wondering why we chose to include a feature that was, in
> the words of Josh Bloch (a respected Java expert), a "failed experiment".
This observation by Bloch was a (correct) comment on thread groups as
the big picture of a class of operations applicable to all threads,
whereas he does acknowledge the usefulness of ThreadGroup.uncaughtException
I believe (unless I am mistaken??), which remains the standard way to
code centralised fault management for unhandled exceptions, doesn't it?
> I'm pleased to see that OOP is making inroads, but I don't see an
advantage
> in a solution that requires OOP when there are technically acceptable
> solutions that do not....
I basically agree. The point I was making is that rejecting a solution
just because it uses OOP is much less compelling than it used to be. I
actually was the one to propose the Task_IDs model for Ada95 in preference
to task classes on the grounds that it was a non-OOP solution, and won.
But that was then - OOP is more acceptable now, and so an argument to
reject a solution just on these grounds is weak.
-- Brian
****************************************************************
From: Tucker Taft
Sent: Friday, May 31, 2002 2:29 PM
> ... 3.2 AI-266: Task termination procedure
>
> This AI proposes a mechanism ("task group") through which the application
> programmer can provide "termination hooks" for procedures that are called
> under a variety of circumstances related to task termination (unhandled
> exception, abort, normal termination, never activated). The task group
> concept is based on the "thread group" notion in Java. The AI stems from a
> discussion at the Exceptions workshop at Ada Europe 2001.
>
> Several issues were discussed:
> (1) Is "silent task death" a problem that needs to be solved?
> (2) Is anything special needed (i.e., can controlled types be used)?
> (3) Is the Java thread group an appropriate basis for the design?
>
> Regarding (1), there was general support for the need for such functionality
> (e.g. for fault tolerance or "health monitoring"); the vote was 13-0. Note:
> this vote applied only to the unhandled exception hook, not to the
> additional items (abort etc.), although subsequent discussion showed support
> for these also.
>
> Regarding (2), controlled types alone are not sufficient. Finalization for
> a controlled object occurs when the object is destroyed, but (e.g. in
> Ravenscar) such destruction will only take place when the object is
> unchecked-deallocated, and unchecked deallocation might be disallowed by the
> implementation or by application requirements. Moreover the "hook" needs to
> be invoked not when the object is finalized, but at the earlier point when
> the task terminates.
This and other comments indicate that this proposal was
not sufficiently explained. Finalization was used to
reset the current task creation group. This was for
stack-resident objects, not heap-allocated objects. Perhaps
the point is that Ravenscar doesn't allow local finalization.
If that is the case, then clearly this proposal needs to be
rethought. I still believe it is desirable to associate
a handler with all local tasks in a given scope, rather than
having to edit the source code of the task (type) body to specify
how it should handle unhandled exceptions.
If you have to edit the task body, then you might as well
throw in a "when others =>" handler. It may be that
you are using a subset that disallows handlers, but
the solution proposed seems no better than writing
a "when others" handler, and is certainly not something
that someone who could write "when others" would be
interested in.
If the proposal provided is too complicated, I would go
towards providing a single global procedure which is called
when a task dies. A slight enhancement would be to have one
handler per master task. Since the only "master task" in
ravenscar is the environment task, this is equivalent to the
one-global-procedure proposal, but for non-ravenscar environments,
allowing other master tasks to specify a different handler for
their sub tasks seems straightforward and useful.
****************************************************************
From: Randy Brukardt
Sent: Monday, June 10, 2002 10:30 PM
I've just finished posting these AIs, and I see a substantial disconnect here
between the IRTAW11 position and the needs of the rest of Ada users.
The write-up for the alternative proposal gives two alternatives in the
existing Ada 95 language, one involving exception handling, and other involving
finalization. The primary reason for rejecting them is that Ravenscar
implementations might not support them. This isn't a very compelling reason.
When someone cuts off their arms (exception handling) and legs (finalization)
[IMHO the two most important runtime features of Ada], they should not complain
that they cannot get around. It also seems weird that such a system would even
allow an exception to be raised in the normal manner, given that the only
possible results are trouble (if it is in the environment task) and more
trouble (if it is in another task). I would expect any exception to be
immediately fatal.
Moreover, the alternative proposal really doesn't add anything whatsoever
unless you are unwilling or unable to use the existing facilities. That means
it offers next to nothing to the significant number of Ada users that have
tasks in their programs but which are not real-time.
The alternative proposal requires that every task be either modified to call or
visible to the monitoring code. This is a very unrealistic requirement in
non-real-time code. (I'd expect it to be unrealistic for real-time code as
well, but I don't want to claim to speak for the IRTAW.) The original proposal
provides a way to add such a handler to all tasks in the system without
modifying any tasks, or even knowing what they are. This is a much more
practical debugging tool for systems which are constructed from pre-existing
parts.
For example, Claw contains a variety of tasks, all hidden in the bodies of Claw
packages. There purposely is no visibility on these tasks. It would be valuable
to insure that every task would notify a debugging manager if it unexpectedly
disappeared. Certainly, if each task has to be modified to accomplish that, it
is likely that some tasks would be missed or purposely omitted, and in such a
case, Murphy's Law is almost certain to strike.
Moreover, an end user using Claw has no way at all to determine if one of
Claw's tasks has crashed. If the exception handler failed (more on this in a
moment), the program can easily end up deadlocked.
In any case, neither proposal deals with the number one problem with the
existing Ada semantics. Most of the technical support calls that RRS has had
vis-a-vis tasking have had to do with tasks silently disappearing when their
task stack space is exhausted. The standard solution of an "others" handler
does not necessarily work in this case.
task body Tsk is
begin
...
exception
when others =>
Ada.Text_IO.Put_Line ("*** Exception raised in TSK!!!");
end;
What happens frequently is that Storage_Error is raised when Tsk's stack is
exhausted; then the "others" handler is executed, but the call on Text_IO
(which is accomplished by the task, thus using the task's stack) raises
Storage_Error again. This second raising causes the tasks to terminate silently
anyway, and no message is output. The program deadlocks, and the user then
thinks that there is something wrong with the compiler and calls us up.
The alternative proposal (which is exactly this code dressed up as a package)
would obviously suffer from precisely the same problem. The original proposal
has the same problem, requiring that Current_Task be the terminating task.
Again, the call to the termination handler could raise Storage_Error.
In view of this problem, I view both of these proposals as unacceptable. About
all either proposal would accomplish would be to give users a false sense of
security that they would find out about task problems. But in practice, the
most important such problem would continue to go undetected. I would expect
that the number of support calls from this problem would go up, rather than
down, if either of these proposals was adopted.
It would be possible to fix the original proposal to solve this problem, but
any fix would necessarily complicate an already complicated proposal. The
alternative proposal would require many additional capabilities in order to be
worthwhile, including (but not necessarily limited to) handlers for all tasks
in a program, preferably handlers for a subset of tasks (based on elaboration
or masters), and some fix for the Storage_Error problem. I would expect any
such proposal to be too complex for the IRTAW group.
I'm well aware that real-time folks are an important customer for Ada, and I
don't believe that we want to adopt a solution contrary to their interests. I
also do not believe that there is any value to adopting a solution that does
not solve any real problems for other Ada users (especially if it appears to
promise to solve those problems).
Therefore, I believe that further work in this area is a complete waste of
effort. When these proposals come up for consideration at the next meeting, I
intend to introduce a motion classify them both as "No Action".
If you believe otherwise, please make an effort to solve the real problems
here.
Randy Brukardt
(Speaking for himself and for R.R. Software, not as ARG Editor)
****************************************************************
From: Tucker Taft
Sent: Tuesday, June 11, 2002 12:12 AM
Can you comment on my proposed "simplification:"
Each master task may specify a procedure to handle task termination
(presumably using an access-to-procedure value rather than dispatching).
The specified procedure is called when any subtask terminates. If a master
task doesn't specify such a procedure, the procedure of its master is called,
and so on up the chain to the environment task.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, June 11, 2002 12:32 AM
> Can you comment on my proposed "simplification:"
Well, it's the middle of the night, but I'll try.
> Each master task may specify a procedure to handle task termination
> (presumably using an access-to-procedure value rather than dispatching).
> The specified procedure is called when any subtask terminates. If a master
> task doesn't specify such a procedure, the procedure of its master is called,
> and so on up the chain to the environment task.
That would solve my minor concern, and certainly would be a new capability. I
wonder if there would be fine enough control if that was the only possibility
(although of course the finalization object approach is always available).
But who calls this procedure? Is there some way we can make it work in the
Storage_Error case (assuming that the master task isn't also raising
Storage_Error)?
****************************************************************
From: Tucker Taft
Sent: Tuesday, June 11, 2002 7:18 AM
>That would solve my minor concern, and certainly would be a new capability.
>I wonder if there would be fine enough control if that was the only
>possibility (although of course the finalization object approach is always
>available).
>
I agree it is a minimial capability. But it seems that it would still be
useful. You could have a registry indexed by task ID if you wanted to
get fancier, with the handler procedure looking up the task that terminated,
and taking some appropriate action based on the ID.
>But who calls this procedure? Is there some way we can make it work in the
>Storage_Error case (assuming that the master task isn't also raising
>Storage_Error)?
I would stick with the execution model described for the original proposal.
I don't understand the big problem with storage_error. Presuming you can
handle storage_error at all, the stack must be cut back somewhat before
entering the exception handler. I presume the call to this termination
procedure would come from the exception handler of "last resort" that the
run-time system provides for every task.
****************************************************************
From: Ted Baker
Sent: Tuesday, June 11, 2002 9:00 AM
| Each master task may specify a procedure to handle task
| termination (presumably using an access-to-procedure value
| rather than dispatching). The specified procedure is called
| when any subtask terminates. If a master task doesn't specify
| such a procedure, the procedure of its master is called, and so
| on up the chain [of masters] to the environment task.
It is a bit ironic to finally introduce to Ada what seems to be an
asynchronous signal handler (like a SIGCHLD handler in Unix/POSIX).
On the other hand, it might be implementable very simply, *provided*
the ARG doesn't end up trying to bind up all the ragged semantic
edges too neatly. Among the various semantic details that I
hope will be left simple and ugly:
1) Which thread executes the handler. It should be
implementation-defined which thread of control executes this
handler. It should not be specified as being executed by the task
that terminates or the task that bound the handler, or we get into
a whole mess of details, including how it can be interleaved with
the normal execution of that task, and how the priority relates to
the priorities (active and base) of that task. In particular, it
should be allowed that the terminating task's thread call the
handler as part of the termination processing, or that this
handling be done by a tasking kernel, or by a waiting server
thread provided by the runtime system.
2) Priority of the handler. It might seem this should be at least
as high as the lower of priority of the task that terminates, but
I can imagine cirumstances under which failure of a high priority
task should not be allowed to cause other high priority tasks to
miss deadlines while the handler executes.
3) Exception propagation. If this handler attempts to propagate an
exception, it should be quietly lost. (Or else we end up having
yet another handler procedure, to receive notification of failed
termination handler procedures?)
4) Abort semantics. If the thread that executes the termination
handler is an Ada task that is aborted while the handler is
executing, the effect should be implementation-defined.
5) Synchronization of shared variables touched by the handler.
The handler may be limited to volatile and atomic variables.
If that is not general enough, perhaps the handler could be
allowed to be a protected procedure?
6) The effect of blocking (and deadlock) in the handler. What if
the handler calls a task or protected entry, a delay statement, or
some other blocking operation? Depending on which thread
executes the handler, the side-effects could be quite varied.
It would be simplest to forbid calls to poentially blocking
operations.
****************************************************************
From: Robert A. Duff
Sent: Tuesday, June 11, 2002 3:00 PM
> It is a bit ironic to finally introduce to Ada what seems to be an
> asynchronous signal handler (like a SIGCHLD handler in Unix/POSIX).
I was assuming the termination handler is called by the terminating
task. Thus, "is called when any subtask terminates" ought to be "before
the task terminates." Otherwise, we get into the can of worms Ted
describes.
I must say, I don't much like this feature...
****************************************************************
From: Tucker Taft
Sent: Tuesday, June 11, 2002 3:30 PM
This is all discussed in a fair amount of detail in the
original proposal, and I would recommend we stick with
those semantics. Of course the run-time system
can always use "as-if" rules to fake it, but in general,
the assumption is that the dying task calls the procedure,
at a time when its task attributes are still well defined.
I suppose if we are going with the handler model, we should
probably make this an access-to-protected procedure. That
has another nice feature in that you can easily associate
some data with the procedure, since the associated protected
object is an implicit parameter to such procedures.
This also helps to address the priority issue, since the
ceiling priority of the protected object determines the
priority. Of course, it also limits the priority of
tasks that it can handle.
>
> I must say, I don't much like this feature...
Can you say a bit more than that. Otherwise this
sounds like the usual unhelpful FUD comment ;-).
****************************************************************
From: Robert A. Duff
Sent: Tuesday, June 11, 2002 9:21 AM
> task body Tsk is
> begin
> ...
> exception
> when others =>
> Ada.Text_IO.Put_Line ("*** Exception raised in TSK!!!");
> end;
>
> What happens frequently is that Storage_Error is raised when Tsk's stack is
> exhausted; then the "others" handler is executed, but the call on Text_IO
> (which is accomplished by the task, thus using the task's stack) raises
> Storage_Error again.
If the "..." above uses up all the task's stack space, then that space
ought to be reclaimed before entering the handler. So the Put_Line
should work (unless the task's stack is *very* small, or unless stacks
are growable and the whole program's memory is uses up).
There are many problems with Storage_Error in Ada. It is practically
impossible to write a non-erroneous handler for Storage_Error.
But that's beside the point, here.
By the way, on some of our embedded targets, we run in a default mode
where a message is printed (or some such thing) whenever a task is
killed by an exception. If the purpose is debugging, this seems
sufficient.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, June 11, 2002 6:01 PM
> > task body Tsk is
> > begin
> > ...
> > exception
> > when others =>
> > Ada.Text_IO.Put_Line ("*** Exception raised in TSK!!!");
> > end;
> >
> > What happens frequently is that Storage_Error is raised when Tsk's stack is
> > exhausted; then the "others" handler is executed, but the call on Text_IO
> > (which is accomplished by the task, thus using the task's stack) raises
> > Storage_Error again.
>
> If the "..." above uses up all the task's stack space, then that space
> ought to be reclaimed before entering the handler. So the Put_Line
> should work (unless the task's stack is *very* small, or unless stacks
> are growable and the whole program's memory is uses up).
It is, but the typical problem is that the task itself has objects using up the
entire stack. (This problem is made worse by the fact that Janus/Ada does
procedure-level allocation for blocks, which means that the space for the
largest block is allocated out of the procedures (in this case, the tasks) own
activation space)).
Ada.Text_IO takes quite a bit of stack space, and task stacks are pretty small
by default. (Making them larger by default would not help much, we've had
several customers adjust their declarations to just fit the (default) stack,
and then have problems.) So it is not at all impossible for a call to
Ada.Text_IO to blow the stack, depending on the declarations in the task. This
has even happened to me a few times.
A typical problem case is something like:
task body Tsk is
begin
...
Ada.Text_IO.Put_Line ("..."); -- Raises Storage_Error.
exception
when others =>
Ada.Text_IO.Put_Line ("*** Exception raised in TSK!!!");
end;
In this case, there is plenty of space to handle the exception (we always
insure that by reserving the space when the handler is entered). But both calls
to Ada.Text_IO.Put_Line will raise Storage_Error. The only way around this is
to have another thread get the notification.
> There are many problems with Storage_Error in Ada. It is practically
> impossible to write a non-erroneous handler for Storage_Error.
> But that's beside the point, here.
You're right, its beside the point. I'm interested in practical, not
theoretical, help here.
> By the way, on some of our embedded targets, we run in a default mode
> where a message is printed (or some such thing) whenever a task is
> killed by an exception. If the purpose is debugging, this seems
> sufficient.
I looked into that years ago, but it is not possible with our current task
model. The problem is that unhandled exceptions is not something that the task
supervisor sees. We generate an empty "others" handler surrounding the entire
contents of the task; the task supervisor only sees normal completion of the
task.
To provide a feature like that would require an extra supervisor call
specifically to provide that error message (something we would not consider
then because of the space implications), or having the compiler generate the
message directly into the code (which would require the compiler to gain
knowledge about writing text, something it currently does not know how to do).
Implementing one of these proposals probably would require adding an additional
supervisor call (probably a weeks work), but at least here the significant cost
of doing that could be justified by the need to implement the new language.
---
Tucker said:
>I would stick with the execution model described for the original proposal.
>I don't understand the big problem with storage_error. Presuming you can
>handle storage_error at all, the stack must be cut back somewhat before
>entering the exception handler. I presume the call to this termination
>procedure would come from the exception handler of "last resort" that the
>run-time system provides for every task.
Most of this I explained above. My point is that there are perfectly good
(perhaps not perfect) ways to do this now in the language. Why should we bother
going through all of this effort if we are not going to provide any truly new
capabilities. The ability to provide a global handler would be nice, but it is
isn't worth much if the only thing I cannot handle myself (Storage_Error) is
not handled here, either.
Ted demonstrates all of the annoying reasons why we cannot provide a solution
to the (IMHO) real problem. (I expect that many of the people asking for this
capability are expecting it to work in the Storage_Error case, while no
proposal to date has done so.) Therefore, I (still) see no reason to bother
with a broken solution in this case.
****************************************************************
From: Ted Baker
Sent: Wednesday, June 12, 2002 9:06 AM
| I was assuming the termination handler is called by the terminating
| task. Thus, "is called when any subtask terminates" ought to be "before
| the task terminates." Otherwise, we get into the can of worms Ted
| describes. -- Bob
It would indeed simplify things if the handler is required to be
called by the terminating task, after the last normal user-defined
finalization, and after the 'Terminated attributed becomes true.
The semantics, including restrictions on the actions of
the handler, could then be similar to other finalizers.
I guess this handler should be called immediately after the point
where the finalizers of any user-defined task attributes takes place:
| 17 When a task terminates, the implementation shall finalize all attributes
| of the task, and reclaim any other storage associated with the attributes.
****************************************************************
From: Ted Baker
Sent: Wednesday, June 12, 2002 9:27 AM
I should add to Randy's question and my previous comments, the
big question:
What does this proposal provide that is not already provided
by the finalizer of a user-defined task attribute of a controlled type?
****************************************************************
From: Tullio Vardanega
Sent: Wednesday, June 12, 2002 10:02 AM
Ted Baker wrote:
> I should add to Randy's question and my previous comments, the
> big question:
>
> What does this proposal provide that is not already provided
> by the finalizer of a user-defined task attribute of a controlled type?
That finalizing a contained entity (or an attribute)
to handle the termination of the containing scope
(perhaps only for notification) is an inversion of
abstraction.
That there may be contexts where object finalisation
may be as unwelcome as exception handling, while
an explicit access-to-protected procedure that
executes on task termination may be acceptable
and useful.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, June 12, 2002 1:53 PM
> That finalizing a contained entity (or an attribute)
> to handle the termination of the containing scope
> (perhaps only for notification) is an inversion of
> abstraction.
No, it means that *your* view of Ada is inverted. Tasking is built on top of
(and is less important than) Finalization, not the other way around.
Sometimes you need to think outside of the box...
> That there may be contexts where object finalisation
> may be as unwelcome as exception handling, while
> an explicit access-to-protected procedure that
> executes on task termination may be acceptable
> and useful.
Let me get this straight. You postulate that there are contexts in which the
compiler needs to provide finalization (by calling an access-to-procedure),
because it is too unsafe to use the finalization provided by the compiler
(object finalization)?
In my opinion, if you cut out exception handling and finalization, the
result is no longer Ada. If you want to code in threaded AdaTran, go ahead,
but I don't see any obligation to complicate the Ada language and runtimes
just to make life easier for you. Particularly when the proposed feature
only compounds my technical support problems in this area, offering only the
suggestion of help without any help whatsover. Let the threaded AdaTran
vendor provide whatever features are necessary.
****************************************************************
From: Alan Burns
Sent: Thursday, June 13, 2002 7:13 AM
I thought it might be useful to clarify some (mainly
non-technical) issues surrounding Task Termination,
AI266, and the Real-Time Workshop (IRTAW).
The request for this task termination feature did not
come from IRTAW. We (IRTAW) were asked to comment
on the AI. I believe the requirement for this type of feature
came from a workshop on exception handling.
For the full language we felt that the AI added little, the
use of exception handling and finalization (our arms and legs
as Randy puts it) gives all the expressive power required. What
was attractive about the AI was the ability to assign a handler
to a set of tasks (indeed the whole program if desired) rather
than to each task in turn. But this was not really a real-time issue.
The other issue about the AI is that it proposes an alternative
means of getting certain functionality which may be of interest
if exceptions and/or controlled types were not being used.
In this situation we felt a simpler feature would
be sufficient - hence the alternative proposal.
However, we understood that the ARG required that any such feature
should apply to the whole language rather than a language subset.
In Ravenscar there is an implicit assumption that tasks do not terminate.
One implementation has allowed the user to associate code with
termination as a fault recognition feature - but using a facility
outside of Ada. Our proposal was a means of bringing this, in a
simple way, within the language. Remember Ravenscar does not itself
prohibit exceptions nor controlled types - but some users do
require these extra restrictions.
In summary, for the full language does AI 266 add sufficient new
functionality? and for restricted domains is it too complicated?
****************************************************************
From: Robert Dewar
Sent: Saturday, June 22, 2002 5:29 AM
> What happens frequently is that Storage_Error is raised when Tsk's stack is
> exhausted; then the "others" handler is executed, but the call on Text_IO
> (which is accomplished by the task, thus using the task's stack) raises
> Storage_Error again. This second raising causes the tasks to terminate
> silently anyway, and no message is output. The program deadlocks, and the
> user then thinks that there is something wrong with the compiler and calls
> us up.
This seems a bit of a strawman to me. Any reasonable Ada compiler must have
an option or mechanism for treating unhandled exceptions in tasks as fatal.
****************************************************************
From: Robert Eachus
Sent: Saturday, June 22, 2002 11:48 AM
I don't see how that would help here. This is an instance of why Dave
Emery calls Storage_Error a parachute that opens on impact. If the
programmer is aware of the possibility that Storage_Error will occur in
the task, he can install an error handler that doesn't use Text_IO, or
that aborts the main program. But any exception handler invocation
could cause a new Storage_Error, even if the handler did nothing. A
mechanism that aborts the program and prints a message if entering an
exception handler causes an error is better than nothing, but it will
not catch this case. Here it is a call within the error handler that is
causing the second (unhandled) error, and the user is right to regard it
as a bug--even if it is actually a language bug not a compiler bug.
The real solution is to have the stack size for Storage_Error handlers
larger than the stack size for the task. Maybe a better wording is to
reserve a possibly user specified area at the end the stack so that the
first Storage_Error instance can be handled successfully. To do this
right of course, the effective stack size has to increase for everything
called inside the handler. More elegant, but not strictly necessary is
for the stack size to revert to its original value when the handler is left.
****************************************************************
From: Robert Dewar
Sent: Saturday, June 22, 2002 12:03 PM
> I don't see how that would help here. This is an instance of why Dave
> Emery calls Storage_Error a parachute that opens on impact. If the
> programmer is aware of the possibility that Storage_Error will occur in
> the task, he can install an error handler that doesn't use Text_IO, or
> that aborts the main program. But any exception handler invocation
> could cause a new Storage_Error, even if the handler did nothing. A
> mechanism that aborts the program and prints a message if entering an
> exception handler causes an error is better than nothing, but it will
> not catch this case. Here it is a call within the error handler that is
> causing the second (unhandled) error, and the user is right to regard it
> as a bug--even if it is actually a language bug not a compiler bug.
Sorry I do not see what you are talking about. The problem we have is with
tasks that go away silently (see the thread, and you will see that this was
Randy's concern). The option to regard an unhandled exception in a task as
fatal is exactly what is needed here.
>
> The real solution is to have the stack size for Storage_Error handlers
> larger than the stack size for the task. Maybe a better wording is to
> reserve a possibly user specified area at the end the stack so that the
> first Storage_Error instance can be handled successfully. To do this
> right of course, the effective stack size has to increase for everything
> called inside the handler. More elegant, but not strictly necessary is
> for the stack size to revert to its original value when the handler is left.
Yes, of course this is the solution. And it is typically how things are done
****************************************************************
From: Ted Baker
Sent: Monday, June 24, 2002 6:45 AM
This discussion seems to be diverging. I see you taking about two different
things:
1) How to arrange, with 100% reliability, that a task cannot die without
any notification.
2) How to arrange for a task to perform its own notification of termination,
by finalization, exception handling, or whatever.
It is pretty clear to me that any recovery mechanism that requires
the notification be done by the task that is dying will be subject to some
possibility that the task dies before it can report its own death.
The discussion of Storage_Error seems to be just searching around
for one such example of task termination without possibility of
self-reporting termination. I'm not sure it matters whether we
all agree that recovery from stack overflow (enough to execute
a termination hander/finalizer) can be guaranteed 100%, since there
always is the possiblity of an unforeseen problem killing a task
abruptly.
There seems to be no doubt that you will be more reliability if the
notification mechanism does not rely on the terminated task being
able to do the notification.
PS: With the original FSUthreads Gnat runtime (I'm
not up to date on changes in the past few years) the FSU threads
library protected a page or two of memory at the end of the task
stack, and released those pages for temporary use to recover from
stack overflow. However, there was still no guarantee that those
few pages would be enough to execute an arbitrary user-provided
handle, but if one knew the size of the handler the number of
reserved pages could be adjusted to allow it to run.
****************************************************************
From: Robert Eachus
Sent: Friday, June 28, 2002 7:27 PM
> It is pretty clear to me that any recovery mechanism that requires
> the notification be done by the task that is dying will be subject to some
> possibility that the task dies before it can report its own death.
I agree. The need is to solve both problems. I think I would like to
see implementations encouraged to raise Program_Error in the main
program if the run-time routine for raising Storage_Error fails. If
Storage_Error is correctly raised and handled silently by a task, well
that is the way the language works. This would only deal with cases
where the run-time code can't execute correctly, which sounds erroneous
to me. (You take a trap for an allocation, and the stack pointer is
iout of range.) So for example if you had a handler for Storage_Error
that caused another Storage_Error, you could at least use the debugger.
For the normal case, having some "extra" stack space that is only
available inside Storage_Error handlers again is the right
implementation but can't be adequately defined by the language standard.
In my opinion an implementation such as I described is legal, and
useful. Anyone writing critical code that can raise Storage_Error needs
to test the handler under worst case conditions, so the Program_Error
should only occur during testing in such applications. (From a testing
standpoint, you want to see that an object of size k gets allocated
correctly, an object of size k+1 raises Storage_Error, and allocating an
object of size k safely followed by another allocation is also raises
Storage_Error and is correctly handled.)
****************************************************************
From: Alan Burns
Sent: Tuesday, March 4, 2003 7:37 AM
I attach a new version of the AI produced at the last IRTAW for task
termination. The previous discussion got bogged down as some
people saw the facility as a very general one that would help
debugging, whilst others wanted a simple scheme for simple
(Ravenscar-ish) programs.
The attached leans toward the simple scheme. It does not
attempt to define task groups (this is where there was much
disagreement). It allows default handlers for all tasks in
a partition or per-task handlers to be set.
[This is version /02 - ED.]
****************************************************************
From: Tucker Taft
Sent: Tuesday, March 4, 2003 9:46 AM
I wonder whether you could use a value of Null_Task_ID
to mean "default" rather than having a separate
set of Set_Default... routines.
It also seems odd to make "Current_Task" be the
default value for the task Id parameter. I would
almost always expect this to be set from "outside" the
task. If you adopt the idea that "Null_Task_ID" means
"default" then it might also be a more useful
default for the Id parameter than Current_Task.
****************************************************************
From: Gary Dismukes
Sent: Tuesday, March 4, 2003 12:30 PM
Alan,
The Set_* operations in your proposal all take both a new and old
handler (in and out formals), but in your example you're only passing
an actual for the new handler. Did you mean to define versions of
the Set ops that don't require returning the old handler?
I also note a couple of typos in your example:
"Abort_Occurence" should be spelled "Abort_Occurrence".
In the second Set_Default_ call, the actual should be:
Logger.Note_Abnormal_Termination'Access.
> package body Termination_Logging is
> protected body Logger is
> entry Guardian when Abort_Occurence is
> begin
> Abort_Occurence := False;
> end Guardian;
> procedure Log_Non_Normal_Termination (
> Id: Task_Id; X: Exception_Occurrence) is
> begin
> -- write out error message to operator terminal
> -- log event to file
> end Log_Non_Normal_Termination;
> procedure Note_Abnormal_Termination(Id : Task_Id) is
> begin
> Abort_Occurence := True;
> end Note_Abnormal_Termination;
> end Logger;
> begin
> Set_Default_Unhandled_Exception_Handler
> (Logger.Log_Non_Normal_Termination'Access);
>>> missing actual for Old_Handler
> Set_Default_Abnormal_Termination_Handler
> (Logger.Log_Note_Abnormal_Termination'Access);
>>> missing actual for Old_Handler
> end Termination_Logging;
I also agree with Tucker's comments about eliminating the Set_Default
versions of the ops and using Null_Task_Id instead of Current_Task.
****************************************************************
From: Alan Burns
Sent: Wednesday, March 5, 2003 2:57 AM
Thanks for comments - the error in the example was just due
to a last minute change (defaults also returning old handler)
that did not propagate through to the example
[Editor's note: The example has been corrected.]
Using Null_Task_Id to set the defaults certainly simplifies
the package. But I guess it sort of opens up a potential error
(failing to put the task ID results in default being set).
Setting default and setting a task are quite different operations;
should this difference be identified by a parameter or by the use
of a differently named set of procedures? Does ARG have a view on
this sort of issue?
****************************************************************
From: Tucker Taft
Sent: Wednesday, March 5, 2003 2:57 PM
The existing proposal seems to have a similar danger
where the programmer leaves out the task ID and
presumes it is setting it for all tasks, or all
sub-tasks, or something.
I guess I find it pretty natural that if you leave
off the task ID, it defaults to meaning all tasks.
That certainly makes more sense to me than it meaning
the current task.
I wonder now whether the default should be not for all
tasks, but for all subtasks? The environment task would
set the default for all non-environment tasks, other tasks
could set the default for their own subtasks only.
Presumably subtasks would inherit the default from
their master task unless the master task set up
its own default.
This would give a kind of task group capability, based
on the existing grouping that Ada already provides,
namely all the subtasks of a given task. Of course
it would be irrelevant to Ravenscar since it doesn't
allow subtasks, but it would seem a natural capability
for systems that do allow subtasks.
****************************************************************
From: Alan Burns
Sent: Friday, March 7, 2003 9:25 AM
I wonder if this extra functionality is worth the extra
overhead - a full hierarchy of tasks will lead to many
defaults. The relationship will need to be on master as
a change of default will need to impact on dependent tasks
in the future and ones previously declared. This does sound
complicated. Is there really a need for this? I remember
the previous ARG discussion where blocks and subprograms
as masters were introduced (in the sense of being able
to set defaults for dependent tasks) - the discussion
raised many more problems than solutions.
****************************************************************
From: Tucker Taft
Sent: Friday, March 7, 2003 10:02 AM
This is only for run-times that support
hierarchies. But I agree, it is a good question
whether this kind of functionality is appropriate.
You mentioned the issue related to task groups,
and that made me think that Ada already has a task
hierarchy, meaning that there is no real need
to introduce another grouping mechanism.
Hence it seems to me that establishing default behavior
for task termination, if grouped at all, should be based
on the existing Ada task hierarchy concepts.
Again this is focusing on what makes sense in a full
functionality run-time, as opposed to Ravenscar.
It seems in that situation, establishing defaults within
a subtree of the task hierarchy makes a lot of sense,
and it is quite simple to describe. I don't see much
implementation burden or distributed overhead, since task
termination is relatively rare, and recording the default
handler for the subtasks in the parent task's TCB seems
pretty straightforward. Inheritance can be implemented
either in an "eager" fashion when the default is set
or a subtask is created, or in a "lazy" fashion by scanning
up through ancestor tasks on task termination. A global
flag could be kept to remember whether there are any
termination handlers at all in the program.
****************************************************************
From: Alan Burns
Sent: Monday, March 10, 2003 6:36 AM
Ok, I'll change the AI to reflect this, but a couple of minor
questions Tuck
1. Can you get the task_id of the environment task - so that the
default for a whole program can be set
2. if the default is changed to an existing hierarchy of tasks
does it impact on all these tasks - even the ones that have
themselves set their default?
****************************************************************
From: Tucker Taft
Sent: Monday, March 10, 2003 9:38 AM
Alan Burns wrote:
> ...
> Ok, I'll change the AI to reflect this, but a couple of minor
> questions Tuck
>
> 1. Can you get the task_id of the environment task - so that the
> default for a whole program can be set
I'm not sure I understand this question. To set the default for the
library-level tasks (those with the environment task as master), the
environment task would call the Set_... procedure with null task ID.
To set the handler for the environment task itself (which seems
a bit weird), you would pass in the task ID for the environment
task, which is what Current_Task returns when the environment task
calls it.
> 2. if the default is changed to an existing hierarchy of tasks
> does it impact on all these tasks - even the ones that have
> themselves set their default?
I am again getting confused by the term "default." I presumed either
you set the *handler* for a particular task, or you set the *default handler*
for all subtasks of the calling task. A default would never override
a handler associated with a specific task, nor would it override a default
set by a subtask to control its further subtasks. It shouldn't matter
in what order the handlers are set, except of course if a handler is
set a second time for the same specific task, or if a default handler
is specified a second time for the same calling task's subtasks.
As an example, presume we have library-level tasks A, B, and C,
each with subtasks, A1..A3, B1..B3, and C1..C3 respectively.
When task A1 terminates, it would first see whether there is
a specific handler for A1 itself, then see if there is a default
handler set by its master task (A), and if not, then see if there
was a default set by A's master task (the environment task).
What matters is what has been set up by the time A1 terminates, not
the order in which these things are set up.
****************************************************************
From: Alan Burns
Sent: Tuesday, March 11, 2003 2:46 AM
My email was not clear - I was concerned with the situation that
a default is set for the second time. The model you have in which
a default is only searched for at the time of termination (rather
than all tasks having defaults inherited from master task) works
fine and does not have the problem of the second setting of a default
having to change the defaults of all dependent tasks.
I'll do the AI
****************************************************************