Version 1.1 of acs/ac-00044.txt

Unformatted version of acs/ac-00044.txt version 1.1
Other versions for file acs/ac-00044.txt

!standard 11.4.1(00)          02-08-28 AC95-00044/01
!class amendment 02-07-13
!status received no action 02-07-13
!subject Addition of Current_Exception function to Ada.Exceptions package
!summary
!appendix

!topic Addition of Current_Exception function to Ada.Exceptions package
!reference RM95-11.4.1 RM95-7.6.1
!from Chris Miller 02-07-10
!keywords Current_Exception, Finalization
!discussion


!problem

There is no way within a finalization routine to find if (a) the
finalization
is being called due to the raising of an exception and, if so, (b) the
details of the current exception occurrence.

!proposal

It is proposed that a new "current_exception" function be added to the
Ada.Exceptions package. Also, the existing
Ada.Exceptions.Null_Occurrence
constant should be made aliased.

The function would have the following specification

  package Ada.Exceptions is
    ...
    function Current_Exception return Exception_Occurrence_Access;
    Null_Occurrence : aliased constant Exception_Occurrence;
    ...
  end Ada.Exceptions;

The new function would return either a (pointer to) the current
exception
occurrence or a (pointer to) the Null_Occurrence constant if there is
none.

!example

The following example (see below) shows how this function could be used
in a finalize procedure. It has been compiled and run under GNAT 3.14p
on a PC.


Here are some notes on the example :-

1. The new function and aliased constant have been added to a package called
   "Ada1".Exceptions. This is just to avoid some GNAT warnings. In practice
   it would be included into the existing Ada.Exceptions package.

2. The program demonstrates a cut down version of a common embedded application.
   There is a schedular program (schedular.ads / .adb) that loops "forever".
   The program implements events by calling an application procedure P1.proc1.
   In this example there is only a single event.

   P1.Proc1 is a hierarchy, it calls P2.Proc2 which in turn calls P3.Proc3.

   P2.Proc2 contains a controlled variable derived from the Sampled_Controlled
   package. A finalize procedure has been specified for this.

   P3.Proc3 raises various exceptions each time it is called. It also
   simulates part of the operation of the current_exception function by
   saving the latest exception_occurrence in a global location. In the
   example this is in a global variable. In practice it would likely be
   in the task control block of the current task but in any case it would be
   hidden from direct access and so the implementation details are not
   relevant.

3. The semantics of the program are such that it should never terminate due
   to the raising of a normal exception in an application event. Rather the
   exception should be logged and the next event should be delivered, and
   so on. However assume that there is a user defined exception called
   "fatal_error" (in exception_names.ads) which, when raised by application
   code, causes the schedular to terminate in an orderly manner and
   (for example) switch processing to a warm standby alternate CPU.

4. Within the finalize routine called in P2.Proc2 there is a need to know
   what has caused it to be invoked.

   If no exception has been raised, or if any exception except fatal_error
   has been raised, then we wish to clean up in the normal manner. This
   is to ensure that the system can continue to operate.

   However if fatal_error has been raised then the image is going to be
   terminated anyway and so cleaning up is a waste of time. Instead we
   wish to return control to the scheduler as soon as possible. Calling
   an operating system "exit" routine is not desired as this terminates
   the image immediately. Rather we require that control should pass back to
   the scheduler so that it can terminate the image in a controlled manner.

5. The sample code (in sample_controlled.adb) shows how the proposed
   current_exception function is used to determine what, if any, exception
   has been raised. Different processing options are then possible.

6. In the case of a fatal_error exception being raised, control returns to the
   scheduler, it exits the Main_loop and arranges for a graceful transition
   to a backup CPU.

Sample code follows

------

with Scheduler;
procedure Main is
begin
  Scheduler.Start;
end Main;

------

package Scheduler is

  procedure Start;

end Scheduler;

------

with P1;
with Exception_Names;
with Ada.Exceptions;
with Ada1.Exceptions;
with Ada.Text_IO;
package body Scheduler is

   procedure Start is
   begin
      Main_loop:
      -- "Forever"
      while true loop
        begin
          -- Just to make the demo work.
          Exception_Names.Latest_Exception := Ada1.Exceptions.
            Aliased_Null_Occurrence;
          -- Deliver an "event".
          P1.Proc1;
          Ada.Text_Io.Put_Line("Scheduler : Event delivered OK.");
        exception
          when Exception_Names.Fatal_Error =>
            Ada.Text_Io.Put_Line("Scheduler : Fatal_Error raised, shutting down ...");
            exit Main_Loop;
          when E: others =>
            -- Perform some logging and / or cleanup, but continue processing.
            Ada.Text_Io.Put_Line("Scheduler : Exception " & Ada.Exceptions.Exception_Name(E) &
              " raised. Continuing ...");
        end;
        Ada.Text_Io.New_Line;
      end loop Main_Loop;
      -- Perform required shutdown code, e.g. switch to an alternate CPU.
      -- Had exit been called in the Finalize procedure then this would
      -- not be possible.
      Ada.Text_IO.Put_Line("Scheduler : switching to backup CPU ...");
   end Start;

end Scheduler;

------

with Ada.Exceptions;
package Exception_Names is

  Fatal_Error, Non_Fatal_Error : exception;

  -- Simulate the data required for the proposed Current_Exception
  -- function by storing the latest exception occurrence (or a pointer to
  -- it) in a global location. Note, would not be done this way in practice.

  Latest_Exception : Ada.Exceptions.Exception_Occurrence_Access;

end Exception_Names;

------

package Ada1 is
  -- Dummy parent package for Ada1 tree.
  pragma Pure;
end Ada1;

------

with Ada.Exceptions;
Package Ada1.Exceptions is

  -- Proposed new function. In practice this would be placed into
  -- the *existing* Ada.Exceptions package. Here it has been put
  -- into a separate "Ada1".Exceptions, just to get it to compile
  -- without warnings.

  function Current_Exception return Ada.Exceptions.
    Exception_Occurrence_Access;

  -- Existing Null_Occurrence constant is not aliased. Propose that it be
  -- made so. For the demo, just define another constant that is aliased
  -- so that can have pointers to it.

  Aliased_Null_Occurrence : aliased constant Ada.Exceptions.Exception_Occurrence_Access :=
    Ada.Exceptions.Save_Occurrence(Source => Ada.Exceptions.Null_Occurrence);

end Ada1.Exceptions;

------

with Exception_Names;
package body Ada1.Exceptions is

  -- This is the new proposed function.
  function Current_Exception
    return Ada.Exceptions.Exception_Occurrence_Access is
  begin
    -- This data would normally be hidden from the user. However
    -- for this demo program we have just used a global variable.
    return Exception_Names.Latest_Exception;
  end Current_Exception;

end Ada1.Exceptions;

------

package P1 is

  Procedure Proc1;

end P1;

------

with P2;
package body P1 is

   procedure Proc1 is
   begin
      P2.Proc2;
   end Proc1;

end P1;

------

package P2 is

  Procedure Proc2;

end P2;

------

with P3;
with Sample_Controlled;
package body P2 is

   procedure Proc2 is
     C : Sample_Controlled.My_Controlled;
   begin
     P3.Proc3;
     -- Finalize called when C goes out of scope.
   end Proc2;

end P2;

------

package P3 is

  Procedure Proc3;

end P3;

------

with Ada.Exceptions;
with Exception_Names;
with Ada.Text_Io;
package body P3 is

   Count : Integer := 0;

   procedure Proc3 is
   begin
      Count := Count + 1;
      -- Simulate some possible error conditions.
      if count = 1 then
        Ada.Text_IO.Put_Line("Proc 3. Doing normal processing");
      elsif count = 2 then
        Ada.Text_IO.Put_Line("Proc 3. Raising Constraint_Error");
        raise Constraint_Error;
       elsif count = 3 then
        Ada.Text_IO.Put_Line("Proc 3. Raising Non_Fatal_Error");
        raise Exception_Names.Non_Fatal_Error;
      elsif count = 4 then
        Ada.Text_IO.Put_Line("Proc 3. Raising Fatal_Error");
        raise Exception_Names.Fatal_Error;
      end if;
   exception
     --  *Simulate* the proposed current_exception function by saving the
     -- latest exception occurrence in a global location (In practice this
     -- data would of course not be visible to application code, most likely
     -- would store it in the task control block of the current task).
     --  Then re raise the same exception.
     when E : others =>
       Exception_Names.Latest_Exception := Ada.Exceptions.Save_Occurrence(E);
       Ada.Exceptions.Reraise_Occurrence(E);
   end Proc3;

end P3;

------

with Ada.Finalization;
Package Sample_Controlled is

  type My_Controlled is new Ada.Finalization.Controlled with private;

private

  procedure Finalize (Object : in out My_Controlled);

  type My_Controlled is new Ada.Finalization.Controlled with null
record;

end Sample_Controlled;

------

with Ada.Exceptions;
with Ada1.Exceptions;
with Exception_Names;
with Ada.Text_Io;
package body Sample_Controlled is

   procedure Finalize (Object : in out My_Controlled) is
     A : Ada.Exceptions.Exception_Occurrence_Access;
     use Ada.Exceptions;
   begin
      --  Finalise routine for the controlled object. We assume that if any exception,
      -- excluding Exception_Names.Fatal_Error, is raised then we need to do some
      -- cleanup for the controlled object. This may take some time.
      --  However if the Fatal_Error exception is raised then we wish to skip the
      -- cleanup and return as soon as possible to the scheduler. For example, the
      -- scheduler may wish to return control to the operating system and / or
      -- switch control to an alternate processor. Wish to have this happen as
      -- soon as possible. In this case doing finalization is of no use, since the
      -- image is going to be terminated anyway.
      --
      --  To distinguish between these two cases we use the proposed current_exception
      -- function. This returns the exception occurrence (or a pointer to it) of the
      -- exception being handled. If no exception is being raised then a (pointer to)
      -- Null_Occurrence is returned. As the existing Null_Occurrence constant is not
      -- aliased we have had to fudge this by taking a copy of it.
      --
      --  Calling an Operating System exit operation is not what is required. Instead
      -- we wish to return control to the scheduler and let it shut down the system
      -- in a controlled manner. If exit is called then this is not possible.

      A := Ada1.Exceptions.Current_Exception;
      if A /= Ada1.Exceptions.Aliased_Null_Occurrence then
        if Ada.Exceptions.Exception_Identity(A.all) = Exception_Names.Fatal_Error'Identity then
           Ada.Text_Io.Put_Line ("Finalize : Fatal_Error, returning to exec ASAP");
           -- And so the exception just propagates. (Don't reraise it or you get
           -- Program_Error, ARM 7.6.1(14)).
        else
           -- Perform normal finalization for Object, may take some time.
           Ada.Text_Io.Put_Line("Finalize : Doing normal cleanup");
           -- ... etc.
        end if;
      end if;

   end Finalize;

end Sample_Controlled;

------

Output from program

Proc 3. Doing normal processing
Scheduler : Event delivered OK.

Proc 3. Raising Constraint_Error
Finalize : Doing normal cleanup
Scheduler : Exception CONSTRAINT_ERROR raised. Continuing ...

Proc 3. Raising Non_Fatal_Error
Finalize : Doing normal cleanup
Scheduler : Exception EXCEPTION_NAMES.NON_FATAL_ERROR raised. Continuing
...

Proc 3. Raising Fatal_Error
Finalize : Fatal_Error, returning to exec ASAP
Scheduler : Fatal_Error raised, shutting down ...
Scheduler : switching to backup CPU ...


!discussion

Consider a typical embedded application where there is a cyclic scheduler
that continually delivers events to application code. An event is
implemented as a procedure in a package. During the event, one or more
subprograms are called, control then returns to the scheduler
and it then selects and delivers the next event. This goes on "forever".

Assume that during the processing of an event application code declares
controlled variable(s). When these go out of scope their finalize procedures
are called in the usual way. Within these finalise procedures it is desired
to know if the routine is being called as a result of an exception being
raised (and if so which one) or as a result of a normal_completion
(ARM 7.6.1 (2)). The application may wish to change the processing done
depending upon which case has caused the finalize to be called.

For example, if there is some fatal error then there may be a need to
shut down the application as soon as possible and transfer processing to
another CPU. Hence doing finalization here may be a waste of time as
the image is going to be shut down anyway.

The general principle is that an application should be able to determine
the circumstances that have caused the finalization procedure to be called
and so adjust processing accordingly.

The proposed current_exception function could be used to do this. Calling
it would return (a pointer to) the current exception_occurrence. From
this details about the exception can be determined as required. In the case
of a normal_completion a (pointer to) the existing Null_Occurrence constant
would be returned.

For the case of abnormal completion due to a task abort (ARM 7.6.1 (2))
the current_exception call would also return Null_Occurrence. There may
be other possibilities here. May even wish to have a separate means of
determining if finalize was called due to an abort but this is probably
another topic that is best left to the language lawyers.

If there are any other cases (??) where it is not possible to access a
valid current occurrence then Null_Occurrence could also be returned.
These would need to be documented.

The current_exception routine would be a new function and would have no
impact on existing code. Making the existing Null_Occurrence constant
aliased should also have no impact.

In many ways the function is analogous to the existing current_task
function.


!summary

There should be a new "current_exception" function added to the Ada.Exceptions
package. Also, the existing Ada.Exceptions.Null_Occurrence constant should be
made aliased.

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

Questions? Ask the ACAA Technical Agent