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