Version 1.21 of ais/ai-10266.txt
!standard C.7.3 (01) 05-10-01 AI95-00266-02/12
!standard C.7 (00)
!standard C.7 (01)
!class amendment 01-06-01
!status Amendment 200Y 04-06-25
!status WG9 approved 04-11-18
!status ARG Approved 6-1-2 04-06-13
!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.
!problem
In Ada 95, a task propagating an exception will be silently terminated.
This can be a significant hazard in high integrity systems.
!proposal
(See wording.)
!wording
Change clause C.7 to
task Information
This clause describes operations and attributes that can be used to obtain the
identity of a task. In addition, a package that associates user-defined
information with a task is defined. Finally, a package that associates
termination procedures with a task or set of tasks is defined.
Add new section C.7.3
The Package Task_Termination
Static Semantics
The following language-defined library package exists:
with Ada.Task_Identification;
with Ada.Exceptions;
package Ada.Task_Termination is
pragma Preelaborate(Task_Termination);
type Cause_Of_Termination is (Normal, Abnormal, Unhandled_Exception);
type Termination_Handler is access protected procedure
(Cause : in Cause_Of_Termination;
T : in Ada.Task_Identification.Task_Id;
X : in Ada.Exceptions.Exception_Occurrence);
procedure Set_Dependents_Fallback_Handler
(Handler: in Termination_Handler);
function Current_Task_Fallback_Handler return Termination_Handler;
procedure Set_Specific_Handler
(T : in Ada.Task_Identification.Task_Id;
Handler : in Termination_Handler);
function Specific_Handler (T : Ada.Task_Identification.Task_Id)
return Termination_Handler;
end Ada.Task_Termination;
Dynamic Semantics
The type Termination_Handler identifies a protected procedure to be executed by
the implementation when a task terminates. Such a protected procedure is called
a handler. In all cases T identifies the task that is terminating. If the
task terminates due to completing the last statement of its body, or as a
result of waiting on a terminate alternative, then Cause is set to Normal and X
is set to Null_Occurrence. If the task terminates because it is being aborted,
then Cause is set to Abnormal and X is set to Null_Occurrence. If the task
terminates because of an exception raised by the execution of its task_body,
then Cause is set to Unhandled_Exception and X is set to the associated
exception occurrence.
Each task has two termination handlers, a *fall-back handler* and a *specific
handler*. The specific handler applies only to the task itself,
while the fall-back handler applies only to the dependent tasks of the task.
A handler is said to be set if it is associated with a non-null
value of type Termination_Handler, and cleared otherwise. When a task is
created, its specific handler and fall-back handler are cleared.
The procedure Set_Dependents_Fallback_Handler changes the fall-back handler for
the calling task; if Handler is null,
that fall-back handler is cleared, otherwise it is set to be Handler.all.
If a fall-back handler had previously been set it is replaced.
The function Current_Task_Fallback_Handler returns the fall-back handler that
is currently set for the calling task, if one is set; otherwise it returns
null.
The procedure Set_Specific_Handler changes the specific handler for the task
identified by T. If Handler is null, that specific handler is cleared;
otherwise it is set to be Handler.all. If a specific handler had previously
been set it is replaced.
The function Specific_Handler returns the specific handler that is currently
set for the task identified by T, if one is set; otherwise it returns null.
As part of the finalization of a task_body, after performing the actions
specified in 7.6 for finalization of a master, the specific handler for the
task, if one is set, is executed. If the specific handler is cleared, a search
for a fall-back handler proceeds by recursively following the master
relationship for the task. If a task is found whose fall-back handler is set,
that handler is executed; otherwise, no handler is executed.
For Set_Specific_Handler or Specific_Handler, Tasking_Error
is raised if the task identified by T has already terminated. Program_Error
is raised if the value of T is Ada.Task_Identification.Null_Task_Id.
An exception propagated from a handler that is invoked as part of
the termination of a task has no effect.
Erroneous Execution
For a call of Set_Specific_Handler or Specific_Handler, if the task identified
by T no longer exists, the execution of the program is erroneous.
!discussion
Many safety critical and high integrity systems prohibit (or discourage)
exception handling, and so the use of a "when others" handler at the
outermost level of the task body 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 fall-back handlers (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 fall-back handler is used for all dependent tasks.
Hence setting the fall-back handler 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 fall-back handler for a part of the tree - this is
directly supported by the proposal. The second involves adding to the existing
fall-back handler - this can be programmed by first getting the original
fall-back handler and then creating a new handler that incorporates the
new functionality plus a call to the original handler.
If a null fall-back 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.
Note: This 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 fall-back handlers for task
hierarchies, but does not go as far as task groups.
!example
The following example illustrates how the mechanisms can be used.
The example is 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 --
protected Logger is
entry Guardian_Control;
procedure Log_Termination(C : Cause_Of_Termination;
Id: Task_Id; X: Exception_Occurrence := Null_Occurrence);
procedure Output(C : Cause_Of_Termination;
Id: Task_Id; X: Exception_Occurrence := Null_Occurrence);
private
Abort_Occurrence : Boolean := False;
Finished : Natural := 0;
Exception_Finished : Natural := 0;
end Logger;
task Guardian; --
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_Of_Termination;
Id: Task_Id; X: Exception_Occurrence := Null_Occurrence) is
begin
Finished := Finished + 1;
if C = Unhandled_Exception then
Exception_Finished := Exception_Finished + 1;
elsif C = Abnormal then
Abort_Occurrence := True;
end if;
end Log_Termination;
procedure Output(C : Cause_Of_Termination;
Id: Task_Id; X: Exception_Occurrence := Null_Occurrence) is
begin
--
end Output;
task body Guardian is ...
end Logger;
begin
Set_Dependents_Fallback_Handler(Logger.Log_Termination'access);
Set_Specific_Handler(Current_Task, Logger.Output'access);
end Termination_Logging;
!corrigendum C.7(00)
Replace the paragraph:
Task Identification and Attributes
by:
Task Information
!corrigendum C.7(01)
Replace the paragraph:
This clause describes operations and attributes that can be used to obtain the
identity of a task. In addition, a package that associates user-defined
information with a task is defined.
by:
This clause describes operations and attributes that can be used to obtain the
identity of a task. In addition, a package that associates user-defined
information with a task is defined. Finally, a package that associates
termination procedures with a task or set of tasks is defined.
!corrigendum C.7.3(01)
Insert new clause:
Static Semantics
The following language-defined library package exists:
with Ada.Task_Identification;
with Ada.Exceptions;
package Ada.Task_Termination is
pragma Preelaborate(Task_Termination);
type Cause_Of_Termination is (Normal, Abnormal, Unhandled_Exception);
type Termination_Handler is access protected procedure
(Cause : in Cause_Of_Termination;
T : in Ada.Task_Identification.Task_Id;
X : in Ada.Exceptions.Exception_Occurrence);
procedure Set_Dependents_Fallback_Handler
(Handler: in Termination_Handler);
function Current_Task_Fallback_Handler return Termination_Handler;
procedure Set_Specific_Handler
(T : in Ada.Task_Identification.Task_Id;
Handler : in Termination_Handler);
function Specific_Handler (T : Ada.Task_Identification.Task_Id)
return Termination_Handler;
end Ada.Task_Termination;
Dynamic Semantics
The type Termination_Handler identifies a protected procedure to be executed by
the implementation when a task terminates. Such a protected procedure is called
a handler. In all cases T identifies the task that is terminating. If the
task terminates due to completing the last statement of its body, or as a
result of waiting on a terminate alternative, then Cause is set to Normal and X
is set to Null_Occurrence. If the task terminates because it is being aborted,
then Cause is set to Abnormal and X is set to Null_Occurrence. If the task
terminates because of an exception raised by the execution of its
task_body, then Cause is set to Unhandled_Exception and X is set to the
associated exception occurrence.
Each task has two termination handlers, a fall-back handler and a
specific handler. The specific handler applies only to the task itself,
while the fall-back handler applies only to the dependent tasks of the task.
A handler is said to be set if it is associated
with a non-null value of type Termination_Handler, and cleared otherwise.
When a task is created, its specific handler and fall-back handler are cleared.
The procedure Set_Dependents_Fallback_Handler changes the fall-back handler for
the calling task; if Handler is null,
that fall-back handler is cleared, otherwise it is set to be Handler.all.
If a fall-back handler had previously been set it is replaced.
The function Current_Task_Fallback_Handler returns the fall-back handler that
is currently set for the calling task, if one is set; otherwise it returns
null.
The procedure Set_Specific_Handler changes the specific handler for the task
identified by T; if Handler is null, that specific handler is cleared,
otherwise it is set to be Handler.all. If a specific handler had previously
been set it is replaced.
The function Specific_Handler returns the specific handler that is currently
set for the task identified by T, if one is set; otherwise it returns null.
As part of the finalization of a task_body, after performing the actions
specified in 7.6 for finalization of a master, the specific handler for the
task, if one is set, is executed. If the specific handler is cleared, a search
for a fall-back handler proceeds by recursively following the master
relationship for the task. If a task is found whose fall-back handler is set,
that handler is executed; otherwise, no handler is executed.
For Set_Specific_Handler or Specific_Handler, Tasking_Error
is raised if the task identified by T has already terminated. Program_Error
is raised if the value of T is Ada.Task_Identification.Null_Task_Id.
An exception propagated from a handler that is invoked as part of
the termination of a task has no effect.
Erroneous Execution
For a call of Set_Specific_Handler or Specific_Handler, if the task identified
by T no longer exists, the execution of the program is erroneous.
!ACATS test
ACATS test(s) need to be constructed for this feature.
!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?
****************************************************************
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
****************************************************************
From: Jean-Pierre Rosen
Sent: Monday, December 1, 2003 7:01 AM
Two points not mentionned (unless I missed them): [He is referencing version
/05 - ED]
The AI does not specify what happens if the handler creates tasks.
Presumably, this is erroneous, but it is certainly worth mentionning.
The AI says that the handler is called "as part of the finalization" of the
task object. Does it imply that it happens after all local objects of the
task have been finalized? I know there are other ways of accessing objects
that have been finalized, but the AI should either:
1) require that the handler be called before finalizatin of local objects
2) explicitely state (and hence warn) that local objects have been
finalized.
****************************************************************
From: Randy Brukardt
Sent: Thursday, February 12, 2004 12:20 AM
Thanks. I don't think you addressed Jean-Pierre's message of December 1st.
(It's at the end of the AI's !appendix.) If anything, the rewrite made it
worse. Here's the text:
When a task terminates, its specific handler, if not null, is
executed. If there is no such specific handler, a default handler is
determined by recursively searching for a non null default handler
in the tasks upon which it depends. If such a default handler is
determined it is executed; otherwise no handler is executed.
We had this problem in relation to task attributes (and indeed, we never
actually finished that AI - AI-237). "When a task terminates" is not a very
clear point in time. For task attributes, we decided it actually happens
*after* the task terminates, but here I would expect that it happens before
the task terminates (the task itself would call the handler in the normal
case, at least). Jean-Pierre wanted a clear indication of where this happens
in relation to the finalization of local objects, and that isn't covered.
(And clearly, it is important that creators of these handlers know which.)
Moreover, I could imagine implementing these handlers as a special
finalization that occurs at the "right" place; in that case, it's necessary
to know where they go on that chain (front or back).
Jean-Pierre also wondered what happens if the handler creates tasks. He
suggested erroneous (presumably so that the task's internal master can have
already gone away). Since this is similar to finalization, it doesn't have
to be erroneous as long as it is early enough. (Sounds like a bad idea,
though, especially in the default handlers. There doesn't seem to be any
problem doing task operations in a specific handler.)
****************************************************************
From: Robert Dewar
Sent: Thursday, February 12, 2004 7:08 AM
> In Ada 95, a task propagating an exception will be silently terminated.
> This can be a significant hazard in high integrity systems.
I must say, I find this a dubious claim. If indeed in a high integrity
system, exceptions can be raised, then in practice this would never be
a problem. Either you allow exception handlers, in which case you would
insist that there be a handler in every top level task (and refuse to
pass a system not meeting these rules), or you do not allow handlers,
in which case you have some special mechanism for dealing with
exceptions in any case and can handle this case.
Why is it easier to provide a complex feature for dealing with this
situation using protected records, rather than to rely on the status
quo reflected in the above paragraph.
I really don't see this as a problem AT ALL for high integrity systems.
It is a problem for general purpose tasking programs in Ada, but the
AI as proposed here offers no relief, since it is just as easy to
insist that the top level handlers be provided as it is to insist
that the protected procedure be put in place.
Personally I would be willing to take the plunge here, and admit that
a horrible mistake had been made, and that if an exception propagates
out of a task, the entire program is terminated in an implementation
dependent manner, as we do for the environment task.
There are three classes of programs with respect to such a change
1. Programs that never encounter the situation, they are of courtse
unaffected by the change. This I am sure is the class into which
almost all programs fall.
2. Programs that do encounter the situation, and it is a bug. Such
programs will welcome the change.
3. Programs that count on this silent termination. I wonder if there
are any? If there are, then this change will require them to put
a when others => null; handler in the main task body. Such a handler
should be considered stylistically mandatory in any case if you
ask me.
So I find this entire AI overkill
****************************************************************
From: Tucker Taft
Sent: Thursday, February 12, 2004 8:06 AM
The ability to establish a default handler for
task termination within a given task hierarchy
is the key capability, in my view.
I agree that setting termination handlers for individual tasks
is less valuable, since it is not significantly different
from adding a "when others" to the task body.
However, it still has the benefit of allowing control
over what happens in this form of failure from
outside the offending task. That more easily allows
one task to manage a number of other tasks. Without
this, the offending task would need to know in
what context it is running, which is more difficult for
an instance of a task type.
If you use task types, then I would imagine the programmer
would need to create a similar capability of their
own if we didn't provide a standard one, keying off
the task-id, to detemine who or what should be notified
as a result of an exception propagated to the task body.
This seems like a relatively simple capability that
should be provided as a standard package, since it
fixes a well-recognized hole in the language.
****************************************************************
From: Robert A. Duff
Sent: Thursday, February 12, 2004 8:20 AM
Robert wrote:
> I really don't see this as a problem AT ALL for high integrity systems.
I tend to agree with Robert, here. This AI seems to be adding a fair
amount of complexity for rather small benefit.
> Personally I would be willing to take the plunge here, and admit that
> a horrible mistake had been made, and that if an exception propagates
> out of a task, the entire program is terminated in an implementation
> dependent manner, as we do for the environment task.
IMHO, that's the way it should have been in the first place,
and the incompatibility seems tolerable.
However, last time I argued this point with Tucker, he was appalled at
the idea of terminating the whole program. I think he would prefer the
exception to be propagated to the master. (I wonder if the task's
siblings get aborted in this model, or if the master waits for them to
finish as usual. Neither one sounds ideal to me.)
There are other places where exceptions happen in embarrassing places,
and Ada says they get lost, or turn into some catch-all like
Program_Error. For example, an exception propagated out of an interrupt
handler is clearly a bug, but Ada just drops it on the floor and
continues. I suppose we don't want to open all these cans of worms.
FWIW, I would design these cases differently (with 20/20 hindsight).
****************************************************************
From: Robert A. Duff
Sent: Thursday, February 12, 2004 8:40 AM
> This seems like a relatively simple capability that
> should be provided as a standard package, since it
> fixes a well-recognized hole in the language.
Shrug. All of the above is true, but I can't get too excited about it.
If a task needs to know something about its context in order to handle
errors (or do anything else, for that matter), then you can pass
information to it as a discriminant. (It's admittedly annoying that
discriminants can't be of record type! I hate horsing around with
pointers just to pass a parameter.)
You can even pass in an action to execute, if you like.
If you want to fix the well-recognized hole, why not simply fix it?
I'm not deadset against the feature -- I just don't see any big benefit.
****************************************************************
From: Robert Dewar
Sent: Thursday, February 12, 2004 8:51 AM
Robert A Duff wrote:
> However, last time I argued this point with Tucker, he was appalled at
> the idea of terminating the whole program. I think he would prefer the
> exception to be propagated to the master. (I wonder if the task's
> siblings get aborted in this model, or if the master waits for them to
> finish as usual. Neither one sounds ideal to me.)
Tell Tuck to calm down. There are FAR less violent cases that are
declared erroneous, and therefore could delete the system disk BEFORE
terminating the program :-)
Anyone concerned with catching this can perfectly well install a
top level handler in their task that does whatever they like.
****************************************************************
From: Robert A. Duff
Sent: Thursday, February 12, 2004 9:02 AM
> Tell Tuck to calm down.
Tuck is always calm. ;-)
>... There are FAR less violent cases that are
> declared erroneous, and therefore could delete the system disk BEFORE
> terminating the program :-)
Good point.
> Anyone concerned with catching this can perfectly well install a
> top level handler in their task that does whatever they like.
I think Tuck's point was that one abstraction should not be allowed to
damage another abstraction. E.g., task A invokes task B, and Tuck wants
to put code in task A that protects it from bad behavior of task B.
He doesn't want to put code in task B, because maybe it comes from
an outside organization or is somehow untrustworthy or whatever.
That view makes some sense, although if task B starts doing
Unchecked_Conv of pointers, all bets are off.
Maybe I should shut up and let Tuck speak for himself...
****************************************************************
From: Robert Dewar
Sent: Thursday, February 12, 2004 9:21 AM
Well this compositional view is somewhat less valid for tasks.
Typically tasking is used in real time programs where you know
what is going on with all the tasks in the system, they are not
abstracted away anyway.
Note that a Restriction:
pragma Restriction (No_Unprotected_Tasks);
that insisted on top level handlers in tasks would be useful
(I think I will file that as a GNAT enhancement request :-)
****************************************************************
From: Tucker Taft
Sent: Thursday, February 12, 2004 9:26 AM
> I think Tuck's point was that one abstraction should not be allowed to
> damage another abstraction.
Yes, that is a fundamental requirement in a large system.
I also believe the notion of "terminating the program" as
a way to handle a problem makes less and less sense in the
"modern" software world. What exactly is a program?
It may be a loose collection of components communicating
and collaborating, within one machine, or across machines.
What does it mean to terminate it?
Even in Ada, we have the notion of multiple partitions.
Many customers have Ada portions and non-Ada portions in
the same "program."
For all these reasons and more, "terminating the program"
is an unacceptable "standardized" response to a problem in my view.
Yes, erroneous execution can lead to any sort of disaster,
and by definition, we don't try to specify what happens.
But in any case where we do specify what happens, I don't believe
we should ever consider "terminate the program" as an
appropriate response, except for the clear cases of aborting
the environment task, or library-unit elaboration failure.
So that is why whenever Bob says "I think that error should just
cause the program to terminate" I cringe. ;-)
****************************************************************
From: Robert A. Duff
Sent: Thursday, February 12, 2004 11:55 AM
> I also believe the notion of "terminating the program" as
> a way to handle a problem makes less and less sense in the
> "modern" software world. What exactly is a program?
What I *really* mean is "terminate the partition". Ada has a notion of
"partition", although it is necessarily implementation-dependent what it
maps to on the underlying system. On a "normal" Unix/Windows
environment, I mean "terminate the current process". On an embedded
system, I might mean "execute a HALT instruction" or "go into an
infinite loop" or whatever is appropriate. On the JVM, I probably mean
"terminate the current applet". Note that on the JVM, there's a
security restriction that you can't halt the virtual machine unless you
have the appropriate privileges.
Anyway, I'm at least half convinced by your arguments about handling
exceptions at higher levels.
But I still think the partition level is a more appropriate place to
deal with this sort of fault tolerance, at least in many cases.
I mean, one partition (a Unix process, say) dies a horrible death,
and the other partition has to notice and do something about it.
This way, even if the bug causes one partition to scribble all
over the run-time system data structures and all the task's stacks, the
other partition will be unperturbed.
> It may be a loose collection of components communicating
> and collaborating, within one machine, or across machines.
> What does it mean to terminate it?
>
> Even in Ada, we have the notion of multiple partitions.
> Many customers have Ada portions and non-Ada portions in
> the same "program."
Right -- I didn't really mean "terminate the whole program".
I meant "partition". (Since I usually write single-partition programs,
I tend to use the terms interchangeably, which is wrong in this case.)
****************************************************************
From: Tucker Taft
Sent: Thursday, February 12, 2004 9:34 AM
> ...
> Well this compositional view is somewhat less valid for tasks.
> Typically tasking is used in real time programs where you know
> what is going on with all the tasks in the system, they are not
> abstracted away anyway.
This seems a very narrow view. Almost all server-based systems
(e.g. web servers, database servers, application servers)
use multiple tasks these days, and these systems sometimes use
abstraction quite heavily.
> Note that a Restriction:
>
> pragma Restriction (No_Unprotected_Tasks);
>
> that insisted on top level handlers in tasks would be useful
> (I think I will file that as a GNAT enhancement request :-)
That might be a nice restriction, but that still doesn't
really address the problem of silent loss of an exception,
nor what a task should do in its "when others" handler
to notify its master. I believe this is a common enough
issue that a standardized solution is warranted, rather
than requiring each project to invent their own task
discriminant-based trickery for communicating up the
task hierarchy.
****************************************************************
From: Robert Dewar
Sent: Thursday, February 12, 2004 9:37 AM
> So that is why whenever Bob says "I think that error should just
> cause the program to terminate" I cringe. ;-)
But all your reasons for cringing are theoretical. I can't imagine
a real situation in which this would cause trouble.
Either you intend the silent termination - please use a handler
Or the program has gone disastrously wrong, and terminating it
is as reasonable as any other response. In fact it's probably
better, since exceptions that are not expected can cause major
disaster themselves and can silently hang waiting for some
other task to terminate.
****************************************************************
From: Tucker Taft
Sent: Thursday, February 12, 2004 11:14 AM
My point was that Ada provides ways *within* the language to
handle errors, even really "bad" ones. It is not uncommon
to have exception handlers at an outer level whose job it
is to catch unexpected exceptions, and reset the
state in some way, drop back to a reduced functionality, shut
down gracefully. Why should this particular error (an unhandled
exception in a task body) go outside of the language, providing
no way to handle it at a higher level?
That's not the Ada way, and is bad news for long-running,
at least semi-fault-tolerant software, like a server or
a control system.
****************************************************************
From: Robert Dewar
Sent: Thursday, February 12, 2004 11:33 AM
> That's not the Ada way, and is bad news for long-running,
> at least semi-fault-tolerant software, like a server or
> a control system.
That's theoretical, and in practice bogus (see signature, which I
believe is attributed to Yogi Berra). Any actual implementation will
have ways of catching unhandled exceptions from the main environment
task (which for some reason don't seem to cause you mental distress,
) and these same methods will of course (already are in practice,
any decent implementation deals with this anomoly in Ada) handle
task terminations.
The important thing to realize about the bug of silent task termination
is that this is not a problem in real problems, or critical programs.
It is a problem during testing, and it is a problem for students,
and crops up in odd places it's true. Once we were debugging an ACVC
test that had an uninitailized out parameter. Ada/Ed caught this
and raised an exception, all the tasks then started to disappear
one by one. That's when we realized we had to have an option to
stop that, and as I say, any decent Ada compiler already handles
this fine, so I see no reason to have a complex addition to the
language here.
Robert Dewar
"In theory there is no difference between theory and practice. In practice there is."
****************************************************************
From: Randy Brukardt
Sent: Thursday, February 12, 2004 11:19 PM
Robert Dewar wrote:
...
> That's theoretical, and in practice bogus (see signature, which I
> believe is attributed to Yogi Berra). Any actual implementation will
> have ways of catching unhandled exceptions from the main environment
> task (which for some reason don't seem to cause you mental distress,
> ) and these same methods will of course (already are in practice,
> any decent implementation deals with this anomoly in Ada) handle
> task terminations.
Huh? Of course all implementations print messages or something, but that's
hardly a useful approach for a web server or other long-running, unattended
application. Especially as usch applications often run with no
Standard_Output. And even if an implementation provides some sort of runtime
notification (and I can't imagine how that would work - the environment task
already having completed - and I'm not aware of any compilers providing it),
you're still depending on a particular compiler's mechanism. For portable
programs like the AdaIC web server, that's a last resort (at best).
The AdaIC web server has never failed to service web requests because of a
software problem, so far as I'm aware. (It's been clobbered by Windows, by
long power failures, by our ISP, and by SBC, but never by an Ada
program-related bug). That's in part because any tasks that raised
exceptions and failed while trying to log that fact simply go away instead
of terminating the program. (I can't say whether this actually has happened;
by definition, such a failure would not be logged.)
In any case, this requirement was originally included in the Ravenscar
proposal by IRTAW. It was split out, and various solutions have been hashed
about for a long time. Any benefits to regular programs are really a
side-effect of providing facilities that the real-time communitity say they
want. (A way to be notified of task termination.)
Anyway, the primary problem I see in practice is that there is no way to
have enough handlers in a task (unless you give up and put in a null
handler), simply because there always is the possibility of an exception
being raised by the handler. This is a special problem if the exception is
Storage_Error from running out of stack space, because any attempt to do
anything is going to re-raise the exception.
The problem I see with the proposal is that it doesn't really address this
problem. (And I'm not sure that any proposal can.) The problem is that the
task itself cannot execute this handler, because there may not be enough
space to run the handler. If that's the case, you'll never get your
notification. The real requirement, as I see it, is for out-of-band
notification of task termination, but short of having a task somewhere
running for that purpose, I don't see any way to implement (or describe)
that. (And having a task simply for that purpose has too much overhead,
especially on threaded systems.)
Anyway, in the absence of something better, I endorse this proposal. But I
really see no point in changing the "disappearing exception" behavior. It
would break servers and other programs that are coded such that a task going
away is non-fatal. Obviously, they could be recoded to avoid the problem,
but forcing people to re-analyze all of their error handling for a dubious
benefit doesn't seem to be a very smart trade-off.
****************************************************************
From: Alan Burns
Sent: Thursday, February 12, 2004 11:44 PM
> We had this problem in relation to task attributes (and indeed, we never
> actually finished that AI - AI-237). "When a task terminates" is not a very
> clear point in time. For task attributes, we decided it actually happens
> *after* the task terminates, but here I would expect that it happens before
> the task terminates (the task itself would call the handler in the normal
> case, at least). Jean-Pierre wanted a clear indication of where this happens
> in relation to the finalization of local objects, and that isn't covered.
> (And clearly, it is important that creators of these handlers know which.)
> Moreover, I could imagine implementing these handlers as a special
> finalization that occurs at the "right" place; in that case, it's necessary
> to know where they go on that chain (front or back).
As you suggest, doing it early would seem sensible.
So would the following be sufficient:
As part of task termination, but before any local objects are
finalised, the task specific handler ...
>
> Jean-Pierre also wondered what happens if the handler creates tasks. He
> suggested erroneous (presumably so that the task's internal master can
> have
> already gone away). Since this is similar to finalization, it doesn't have
> to be erroneous as long as it is early enough. (Sounds like a bad idea,
> though, especially in the default handlers. There doesn't seem to be any
> problem doing task operations in a specific handler.)
I assumed we would take the same approach as finalisation routines.
As for the more general discussion about this facility. One driver
was for Ravenscar where the Raven implementation was asked to
provide this type of provision. Some people wanted it just for
debugging, others (perhaps only in theory) saw ways of programming
degraded service and some forms of fault tolerance. Overall it
seems like a useful (though not perfect) facility to add.
****************************************************************
From: Randy Brukardt
Sent: Friday, February 13, 2004 12:28 AM
> As part of task termination, but before any local objects are
> finalised, the task specific handler ...
Sounds OK to me. Do you want me to just update the AI.
> I assumed we would take the same approach as finalisation routines.
Which is that there is no special approach. That's fine; Ravenscar impls
can't create tasks in a handler anyway.
Wait a minute: this handler is a protected procedure. It's a bounded error
to create a task in any protected procedure, of course, so there is no issue
here.
> As for the more general discussion about this facility. One driver
> was for Ravenscar where the Raven implementation was asked to
> provide this type of provision. Some people wanted it just for
> debugging, others (perhaps only in theory) saw ways of programming
> degraded service and some forms of fault tolerance. Overall it
> seems like a useful (though not perfect) facility to add.
Thanks for the clarification.
****************************************************************
From: Tucker Taft
Sent: Friday, February 13, 2004 5:22 AM
> So would the following be sufficient:
>
> As part of task termination, but before any local objects are
> finalised, the task specific handler ...
That doesn't sound right either. We want this to happen
*after* all local objects have been finalized, but
before the task is considered terminated, just before
the task attributes are finalized.
The implementation could use finalization to accomplish
this, but it should be after all user-defined finalization,
since those might themselves raise exceptions.
****************************************************************
From: Pascal Leroy
Sent: Friday, February 13, 2004 7:05 AM
Alan explained:
> One driver was for Ravenscar where the Raven implementation was
> asked to provide this type of provision. Some people wanted
> it just for debugging, others (perhaps only in theory) saw
> ways of programming degraded service and some forms of fault
> tolerance. Overall it seems like a useful (though not
> perfect) facility to add.
This is hardly a convincing justification, Alan. One reason is for
debugging: I for one don't want to add junk to the language for
debugging; there are debuggers, tracing tools, implementation-defined
interfaces to the runtime system, etc. that provide this kind of
facility in way which is probably more user-friendly, and doesn't
require language support. The other reason is "perhaps in theory"; I
would like to be explained how this facility could be used in practice
by a system to provide a degraded service or some kind of fault
tolerance; I'm sorry, the usage model is not at all obvious to me.
Robert proposed:
> Personally I would be willing to take the plunge here, and
> admit that a horrible mistake had been made, and that if an
> exception propagates out of a task, the entire program is
> terminated in an implementation dependent manner, as we do
> for the environment task.
That's an interesting idea, especially from someone who is usually
opposed to any kind of incompatibility ;-) I agree that it should have
been that way in the first place, and I would be willing to byte the
bullet and accept the incompatibility. (Of course, as others have
noted, "program" should be "partition" above.)
Bob wrote (acting as a spokesman for Tuck):
> ... E.g., task A invokes task B, and Tuck wants
> to put code in task A that protects it from bad behavior of task B. He
> doesn't want to put code in task B, because maybe it comes from an
> outside organization or is somehow untrustworthy or whatever. That
> view makes some sense, although if task B starts doing Unchecked_Conv
> of pointers, all bets are off.
If you put untrustworthy code in a high-integrity system, you have other
problems than task termination ;-)
Seriously, I fail to see how the proposal deals with this situation.
Deep inside the guts of some subsystem provided by an outside source
there is a task B. How the hell do I establish a termination handler
for this task? Establishing a handler requires a Task_ID. How do I get
a Task_ID? If task B is not visible, and if it's not particularly
cooperative (i.e., it doesn't export an operation that returns its own
Task_ID) you are out of luck.
If you don't have the source of this outside subsystem, btw, you may not
even know if it's doing tasking, or using some other kind of parallelism
(playing with interrupts, accessing the thread library directly, etc.).
Hey, this can even change from one version to the next.
If all the code is under your control, true, you can export the
appropriate information. But then I agree with Robert and Bob that you
are probably going to program the interactions between tasks carefully,
and that you can do all the necessary termination control within the
current language.
--
I have another concern with this proposal: it seems very convoluted to
me. Implementation-wise, I am not competent: from 30,000 ft this is
just another piece of junk in the TCB and just another thing to do at
termination. However, the conceptual model is very intricate: each time
this AI has been discussed by the ARG it has taken a lot of efforts to
re-explain the notion of default handler and specific handler, and how
these handlers are set from the outside, and why, etc. It is
significant that we changed the identifiers _each_time_ we discussed
this AI, and I still don't find them very expressive.
Now if the ARG has trouble grappling with this AI, I cannot imagine how
the average Ada user can be expected to do anything useful with it.
Especially considering that pretty much the same effect can be
programmed using the current language. Furthermore, it seems to me that
this feature could be easily misused or abused, as it would introduce
lots of coupling between the parts of a system. This is particularly
hard to manage in large programs.
To summarize, I'd say: who ordered this?
****************************************************************
From: Robert A. Duff
Sent: Friday, February 13, 2004 8:05 AM
Randy wrote:
> The AdaIC web server has never failed to service web requests because of a
> software problem, so far as I'm aware. (It's been clobbered by Windows, by
> long power failures, by our ISP, and by SBC, but never by an Ada
> program-related bug). That's in part because any tasks that raised
> exceptions and failed while trying to log that fact simply go away instead
> of terminating the program. (I can't say whether this actually has happened;
> by definition, such a failure would not be logged.)
This is a very good point, which I had forgotten. It's all well and
good to tell programmers to put a handler at the bottom of their tasks.
But what if that handler raises exceptions?
Ideally, you would prove that the handler itself can't raise
exceptions. But Ada doesn't have any built-in support for that.
> This is a special problem if the exception is
> Storage_Error from running out of stack space, because any attempt to do
> anything is going to re-raise the exception.
I claim Storage_Error is hopeless anyway -- in Ada it is impossible to
write a handler for Storage_Error that does anything interesting (at
least, not without implementation-specific hackery). Not even print an
error and shut down the program, much less to log the error and
continue.
****************************************************************
From: Robert A. Duff
Sent: Friday, February 13, 2004 8:25 AM
Alan wrote:
> So would the following be sufficient:
>
> As part of task termination, but before any local objects are
> finalised, the task specific handler ...
I agree with Tucker, who said that you want the task specific handler to
run *after* all objects local to the task have been finalized.
The wording is tricky. I don't think we can say "as part of task
termination", because termination is not a process. 9.3(5) defines
termination -- a task is terminated when finalization of the task_body
has been performed. A task_body is a master, and finalization of it
means waiting for dependent tasks, then finalizing all the objects in
it. So I think what we want is to make the task specific handler run as
part of finalization of a task_body -- after finalizing the objects.
How about this: "As part of the finalization of a task_body, after
performing the actions specified in 7.6.1 for finalization of a master,
the task specific handler is called." Then AARM annotations can point
out that this means that 'Terminated is False while running the
handler, but that all objects local to the task have been finalized.
I think this also implies that Current_Task is the task in question.
****************************************************************
From: Jean-Pierre Rosen
Sent: Friday, February 13, 2004 9:03 AM
> The implementation could use finalization to accomplish
> this, but it should be after all user-defined finalization,
> since those might themselves raise exceptions.
OTOH, a task may define one of its own internal procedures as a termination
handler, and thus access its own finalizable objects. Minimizing accesses to
objects after they've been finalized seems desirable...
****************************************************************
From: Tucker Taft
Sent: Friday, February 13, 2004 10:53 AM
> OTOH, a task may define one of its own internal procedures as a termination handler,
> and thus access its own finalizable objects.
No, the handler is required to be library-level, I believe, or at the very
least, something that outlives the tasks it is handling.
> Minimizing accesses to objects after they've been finalized seems desirable...
Yes, but we don't allow "local" handlers.
****************************************************************
From: Tucker Taft
Sent: Friday, February 13, 2004 10:51 AM
Pascal Leroy wrote:
>
> That's an interesting idea [killing partition due to exception
> killing task body], especially from someone who is usually
> opposed to any kind of incompatibility ;-) I agree that it should have
> been that way in the first place, and I would be willing to byte the
> bullet and accept the incompatibility. (Of course, as others have
> noted, "program" should be "partition" above.)
I reply again (because I saw no acknowledgement or response to this):
... Ada provides ways *within* the language to
handle errors, even really "bad" ones. It is not uncommon
to have exception handlers at an outer level whose job it
is to catch unexpected exceptions, and reset the
state in some way, drop back to a reduced functionality, shut
down gracefully. Why should this particular error (an unhandled
exception in a task body) go outside of the language, providing
no way to handle it at a higher level?
That's not the Ada way, and is bad news for long-running,
at least semi-fault-tolerant software, like a server or
a control system.
---
I agree the solution should be well-defined, and not overly complex.
But I think the task hierarchy should be obeyed (that is,
a task can control its subtasks). I believe setting an overall default
handler within a hierarchy is more important than setting handlers
for individual tasks, and if it would significantly simplify
the proposal to omit individual task handlers, then let's consider it.
But I believe some way of handling unexpected task termination (*within
the language*) is quite important, and has been a well-known hole in
Ada since early on.
****************************************************************
From: Gary Dismukes
Sent: Friday, February 13, 2004 12:43 PM
I find your arguments convincing and am in favor of providing
some mechanism to address this. I think we should consider
possible simplifications based on an overall hierarchy handler,
but see value in the per-task termination handling capability
and would prefer that if it can be kept reasonably simple.
****************************************************************
From: Alan Burns
Sent: Wednesday, February 18, 2004 7:22 PM
To summaries recent emails. 3 questions seems to relevant.
Do we want this functionality
Do we want something simpler
If we do want something, exactly what are the semantics.
To deal with the last point first. There seems general
agreement that the handler should be called after the
finalisation of local objects. Bob suggests the following
words:
As part of the finalization of a task_body, after
performing the actions specified in 7.6 for finalization of a master,
the task specific handler is called. ...
Anyone not happy with this?
With regard to the other questions
Pascal asked "who ordered this".
There seems to be a few different groups. The main
request came from real-time ravenscar folk, who wanted
a standard way of dealing with terminating tasks (under
Ravenscar no task should terminate). The action to be
taken would usually be a shut-down or restart or lane
switch (and restart), ie fault recognition
within the program, but no recovery within the program.
More general fault tolerance folk see a way of programming
some forms of recovery. If task T fails then create a new task
(non-determinacy is likely to stop this new one failing
at the same point, ie new input data, different internal
state), or create a different task (different type) but
with reduced functionality.
These two requirements both seem reasonable. I'm less
convinced by the "fault in some external subsystem" argument.
What can the programmer do?
I do not think the original semantics are wrong. The task is
a natural fault containment unit. Within a task an exception
is synchronous. To raise an exception within the parent/master would
be asynchronous and error prone. Remember the debate about
select then abort, rather than asynchronous exceptions.
The current proposal allows the handler to be independent of
parent task or (via the use of a PO and ATC) to gain the
attention of the parent in a controlled way. Tuck argues:
> ... Ada provides ways *within* the language to
> handle errors, even really "bad" ones. It is not uncommon
> to have exception handlers at an outer level whose job it
> is to catch unexpected exceptions, and reset the
> state in some way, drop back to a reduced functionality, shut
> down gracefully. Why should this particular error (an unhandled
> exception in a task body) go outside of the language, providing
> no way to handle it at a higher level?
>
> That's not the Ada way, and is bad news for long-running,
> at least semi-fault-tolerant software, like a server or
> a control system.
>
> ---
>
> I agree the solution should be well-defined, and not overly complex.
> But I think the task hierarchy should be obeyed (that is,
> a task can control its subtasks). I believe setting an overall default
> handler within a hierarchy is more important than setting handlers
> for individual tasks, and if it would significantly simplify
> the proposal to omit individual task handlers, then let's consider it.
> But I believe some way of handling unexpected task termination (*within
> the language*) is quite important, and has been a well-known hole in
> Ada since early on.
The current proposal could be simplified by removing the specific
handler and just have the default. This would be fine for ravenscar.
I will be interested to see what the vote is at the next meeting!
****************************************************************
From: Robert Dewar
Sent: Thursday, February 19, 2004 6:31 AM
Pascal Leroy wrote:
<<much excellent discussion snipped>>
> To summarize, I'd say: who ordered this?
I 100% agree with everything Pascal said here.
****************************************************************
From: Robert Dewar
Sent: Thursday, February 19, 2004 7:06 AM
> ... Ada provides ways *within* the language to
> handle errors, even really "bad" ones. It is not uncommon
> to have exception handlers at an outer level whose job it
> is to catch unexpected exceptions, and reset the
> state in some way, drop back to a reduced functionality, shut
> down gracefully. Why should this particular error (an unhandled
> exception in a task body) go outside of the language, providing
> no way to handle it at a higher level?
Actually in critical software, these "standard" mechanisms
are often not used. Why not? For several reasons:
exception handling is complex, and something that you
usually want to exclude from the certification arena.
if you do have exceptions, then they raise annoying
testing issues and deactivated code issues, noticeably
raising the costs of handling them.
generally you DO try to prove that exceptions are
not possible (see praxis site for an interersting
example of this approach carried all the way).
exceptions are not really the right model anyway
for handling disastrous errors requiring a system
reset. They can get swallowed up by others handlers,
or by task termination (the current discussion) or
propagating them can simply hang due to waiting for
tasks to terminate.
So generally the more common model is to see some kind
of last chance handler that is most definitely outside
the language.
Now this is certainly not always the case, and in fact
we have a major customer doing certification who needs
a limited form of general exception handling to deal
with some legacy code. This will indeed result in
increases in certification costs, and we will have to
be very careful the above problems do not arise!
****************************************************************
Questions? Ask the ACAA Technical Agent