Version 1.3 of ais/ai-10266.txt

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

!standard D.2.2 (5)          02-06-01 AI95-00266-02/01
!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 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-01 which was rejected by IRTAW11.
!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

!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
       <bunch of declarations here>
    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?

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


Questions? Ask the ACAA Technical Agent