Received: from ATHENA-AS-WELL.MIT.EDU by po7.MIT.EDU (5.61/4.7) id AA18887; Sat, 7 Nov 92 11:16:58 EST
Received: from CECI.MIT.EDU by Athena.MIT.EDU with SMTP
	id AA06947; Sat, 7 Nov 92 11:16:54 EST
Received: from nora.hd.uib.no by ceci.mit.edu id AA17532g; Sat, 7 Nov 92 11:19:36 EST
Received: from [129.177.24.36] (mac36.hd.uib.no) by nora.hd.uib.no (5.65c/1.34)
Date: Sat, 7 Nov 1992 17:17:56 +0100
Message-Id: <199211071617.AA04364@nora.hd.uib.no>
Sender: sigmund%nora.hd.uib.no@ceci.mit.edu
To: Judson Harward <jud@ithake.MIT.EDU>
From: sigmund%nora.hd.uib.no@ceci.mit.edu
Subject: Re2: Failing with Dignity
Cc: msc@ceci.mit.edu, achiqui@roman.uib.no

Jud,

You mention three classes of failure:

>	1 ) trying to access an unavailable resource, e.g., an extra
>	8 Mb of memory, a busy videodisc player, a locked database

This is the kind of failure I a most concerned about.

>	2 ) error in user ADL code

Seems as it would be part of the parser's normal duties to detect such
errors.

>	3 ) a mismatch between the application assumptions and the
>	capabilities of the platform, e.g., a color application
>	that wants to display 24 bit images on an 8 bit color
>	system.  This is similar to ( 1 ), but maybe slightly different.

First, AM2 itself imposes some requirements to the system, like having
QuickTime installed.  If the system lacks the required resources, AM2 must
refuse to run.
Second, an AM2 application may require additional resources, which leads to
a situation where the requirements are posed after AM2 has started.  Taken
the example with colors, Macintosh QuickDraw normally translates the 24 bit
image to support the number of bits on each monitor connected.  The
question is whether we should allow the application programmer to make a
list of demands, say refusing to show the application if not the deepest
monitor connected is at least 24 bits and supports colors.  Some kind of
demands are rather difficult to pose, like the amount of memory.


Back to Case 1 where AM2 is trying to access an unavailable resource, you
have an important point:

>Case 1 is very system dependent.  On UNIX, when you run out of memory,
>garbage collecting may help, but with abundant virtual memory, it's far more
>likely that there is a gross mismatch between what you are asking the system
>to do, and what it can do.

If I understand you right, UNIX normally gives you all the memory you
demand, unless something is wrong with your demands.  Wish I was there.  On
the Macintosh every request for memory has the possibilty to fail and must
be checked by the program.  Sorry, but that's the law here in this town. 
Accordingly, MacApp failure handling is tightly coupled with memory
management that relieves some of the burdens off the programmer's
shoulders.

In MacApp a failure is regarded as an exception, and program control is
taken away from the offending block of code.  That is, constructions like
this one are NOT used:

        <Do an operation that may fail>;
        if (<failure>)
            <Do something>;
        else
            <Do something else>;

Instead, MacApp's failure handling mechanism allows functions to clean up
debris left by a code failure and continue execution at a specified point. 
A failure handler is a block of code, generally local to some function,
that is called when a failure occurs and that takes action to handle the
failure.  References to failure handlers are kept on a stack.  When a
failure is likely to occur and particular cleanup will have to be done,
MacApp lets you push failure handlers onto the stack.

The topmost failure handler is invoked by a call to the function Failure. 
Here is its interface and its 411 information:

        pascal void Failure (OSErr error, long message);

        File: "{MACPlusIncludes}UFailure.h"; File
"{MALibraries}UFailure.cp"

        Failure calls the most recent failure handler. MacApp 
        calls Failure from many methods as a means of gracefully 
        recovering from error conditions.

        Call?
        	As needed. 

        Parameters: 
        	error: specifies the error condition that caused 
        	Failure to be called.

        	message: specifies the message that is to be displayed.

 An application will most often call Failure indirectly by using one of the
MacApp provided functions that check some condition and call Failure if the
condition isn't met.  The functions provided are FailMemError, FailNIL,
FailNILResource, FailNonObject, FailNoReserve, FailOSErr, FailResError, and
FailSpaceIsLow.  As an example, let's have a look at the implementation of
FailNIL.  By the way, I apologize for the cultural background of MacApp and
me.  We were raised with Pascal, so we say NIL when we mean NULL:

        pascal void FailNIL (void* p)
        {
            if (!p)
                Failure (memFullErr, 0);
        } // FailNIL 


Let me illustrate the failure handling mechanism by an example.  This is
how a block of code could look like if we were not concerned about failure
handling:

        {
            Handle h1 = NewHandle (numBytes1);
            Handle h2 = NewHandle (numBytes2);
        }

And this is how it blows up when we are concerned about shielding the user
from bombs and freezing screens.  We must take particular care with the
situation when h1 gets its memory allocated, but h2 fails.  The whole
opertation will be canceled, and we are responsible to dispose of the
memory that has been allocated for h1:

        {
 (1)        Handle h1 = NULL;
 (2)        VOLATILE (h1);
 (3)        Handle h2;
 (4)        FailInfo fi;
 (5)        if (fi.Try ())
            {
 (6)            h1 = NewPermHandle (numBytes1);
 (7)            h2 = NewPermHandle (numBytes2);
 (8)            fi.Success ();
            }
 (9)        else
            {
(10)            DisposeIfHandle (h1);
(11)            fi.ReSignal ();
            }
        }

I have no excuses for all this, but let me try to explain:


 (1)        Handle h1 = NULL;

The handle must be initialized because if (6) fails inside NewPermHandle,
(10) will be executed, and at that point h1 must be a valid Handle or NULL.


 (2)        VOLATILE (h1);

Because the Try function saves the state of registers and the
failure-handling mechanism restores them when Try returns FALSE, we must
not allow variables that we want to use during recovery actions to be
stored in registers.  To prevent a variable from being placed in a
register, we can specify the variable as an argument in the VOLATILE macro.
 Unfortunately MPW 3.2 C++ has not implemented the volatile keyword, but
MacApp emulates the volatile behavior with the macro:
 #define VOLATILE (a) ((void) &a)

 (3)        Handle h2;

There is no technical reason to initialize h2.  However, many MacApp
programmers would buy the extra security not having to deal with
uninitialized variables.  The cost is 4 extra bytes of code and a couple of
the processor's clock cycles.


 (4)        FailInfo fi;

The FailInfo class implements a failure-handling mechanism, which handles
failures in nested code.  The class is platform dependent, needless to say.


 (5)        if (fi.Try ())

The Try method sets up the failure handler and always returns TRUE
initially.  If a failure occurs, the failure handler reinvokes the Try
method, which returns FALSE.

The effect of this is that (10)-(11) will be executed only when (6)-(7) has
been partly executed and have failed.


 (6)            h1 = NewPermHandle (numBytes1);
 (7)            h2 = NewPermHandle (numBytes2);

The MacApp function NewPermHandle is called instead of the Toolbox function
NewHandle.  One difference has to do with the fact that MacApp logically
divides the heap into "permanent" and "temporary" memory.  The names are
somewhat misleading.  Permanent memory is normal memory for everyday use. 
Temporary memory is used in situations where testing for failure would be
impossible or that we wouldn't been able to recover from failure, as when
loading non-resident segments of program code or other memory allocations
internal to the Toolbox and operating system.  The principle is that there
should always be enough free space to satisfy all demands of temporary
memory.  In order to achieve the goal, MacApp keeps a memory reserve that
is large enough.  Of course the programmer first needs to estimate what
that maximum will be and tell MacApp.  NewHandle would have allocated
temporary memory, which we don't want.  NewPermHandle allocates permanent
memory.

Another difference is that NewPermHandle uses the failure mechanism
internally so we don't need to call "FailNIL (h1);" in our own code. 
NewHandle is only a Toolbox function that knows nothing about MacApp nor
its nice failure handling capabilities.

The source code of NewPermHandle can serve as an example of dealing with
memory:

        pascal Handle NewPermHandle (Size logicalSize)
        {
            static const short initVal = 0xF3;			// odd at all byte
boundaries			

            Boolean priorPerm;
            Handle aHandle;

            priorPerm = PermAllocation (TRUE);
            aHandle = NewHandle (logicalSize);
            pPermAllocation = priorPerm;
            FailNIL (aHandle);
        #pragma push
        #pragma R-
            if (qDebug)
                BlockSet (*aHandle, logicalSize, initVal);
        #pragma pop
            return aHandle;
        } // NewPermHandle 

Notice that when compiled in debug mode, the new memory is set to values
that produces spectacular bombs when used as uninitialized pointers.  The
line "pPermAllocation = priorPerm;" is quick, but dirty.  "PermAllocation
(priorPerm);" would be less efficient, but cleaner.


 (8)            fi.Success ();

Success cleans up the failure stack after this failure handler is no longer
needed.


 (9)        else

Remember, the flow of control in this piece of program is not normal.  When
the "else" part is executed, the "if" part has already been partially
executed and interrupted because of some failure.


(10)            DisposeIfHandle (h1);

Automatic garbage collection is unknown in the Macintosh community. 
Nevertheless the memory resources are so scarce that everything must be
recycled, and we must do it ourselves.

DisposeIfHandle is a MacApp function that disposes of the specified handle
if it is non-NULL.  It returns a NULL pointer for convenient assignment to
the handle for which DisposeIfHandle is called.  Since there isn't any more
references to h1, I skipped the assignment, but in order to protect my
program somewhat better against myself, I could have written "h1 =
DisposeIfHandle (h1);"

When this statement is executed, we know that (6) or (7) has failed.  If
(6) has failed, h1 is NULL, and DisposeIfHandle does nothing.  If (7) has
failed, DisposeIfHandle disposes of h1 in order to recycle memory as we
cancel this whole operation.

Without the initialization in (1), h1 will be uninitialized when (6) fails.
Without (2) an optimizing compiler can create code that breaks the failure
handling.

There is no need to dispose of h2 since it has no memory allocated on any
kind of failure.


(11)            fi.ReSignal ();

ReSignal invokes the most recent failure handler, which propagates the
failure to the next handler.  The practical consequence is that we exit the
function at this point, leaving the control to the next failure handler.

Under some special circumstances we want to resume program control
ourselves, not have it transfered to the next failure handler.  Then we
just don't call ReSignal.



Do we wish to code like this?  Obviously not.  But on the Macintosh failure
handling like this is an art of necessity for a user friendly program. 
Many programs don't, and they are a pain.

Being a Macintosh programmer, I have accepted my faith.  The big question
is what our common code should look like.  There is no problem defining a
cross-platform FailInfo class with inline functions that do nothing and
generate no code.  The problem is that this kind of paranoism slows down
program development on other platforms where it isn't necessary.  What do
you UNIX programmers think?



By the way...

We don't always have to set up our own failure handler.  My example was
chosen to demonstrate the worst case.  If h2 wasn't there, only h1, all
cleanup necessary would be done by already existing failure handlers, and
our code block would be reduced to:

        {
            Handle h1 = NewPermHandle (numBytes1);
        }


Bergen, on a cold and dark November evening,

        Sigmund

