I've set up an exception handler to catch any out of memory exceptions. In this handler, I clean up
then call Commit(). By doing this, I get a commit underflow. What does this mean?
The Commit() in the exception case above is incorrect. You should only issue a Commit() if
everything goes okay, or if you need to commit surrounding exception handlers inside a nested one
where things went wrong. If an exception occurs that you catch then there is nothing to commit
because the Catch() or Try() has been removed from the exception stack by the time you get control.
Think of it this way:
Catch() or Try() --> push a handler onto the exception stack
Commit() or exception --> pop a handler off the exception stack (and calling it in the exception
case)
Here is a small example from that demonstrates committing nested handlers:
I've set up an exception handler to catch the times when I can't allocate the transient memory I've
requested. When this happens, I want to put up an announcement to tell the user that there's not
enough memory available to complete the operation. Right now, when I try to present this
announcement, Magic Cap throws another cannotAllocateMemory exception and goes into the debugger.
I simplified my situation down to the following code, but I still get that exception when the code
calls Announce(). Is this because there's not even enough memory for Magic Cap to put up the
announcement? Is there an upper limit to how much memory I should allocate before stopping?
The answer to your immediate question is that there's no set limit, but if your package uses up
all of transient memory, Magic Cap will not be able to function. Because of object shadowing, even
destroying objects can consume a small amount of memory, so you should never try to do very much
after catching an out of memory exception until you free the memory you were allocating.
The most dangerous part of your code is the way you set up the exception handler with Catch().
If you call Catch() but there was no exception thrown, you must call Commit() to clear the Catch()
from the internal exception stack. Your crash may have something to do with this bug. Whenever you
call Catch() and an exception is thrown, Magic Cap removes the exception handler from the exception
stack, places the PC back at the return from the Catch() and the execution continues from there.
One possible way to fix this problem is tocall to Commit() after calling NewTransientBuffer()
to remove the exception frame after the memory allocation succeeds. What follows is a better way of
structuring this type of code.
Try() seems to be broken. Even though I've set up an exception handler using Try(), I still
break into the debugger whenever an exception is thrown.
This is actually a feature of the Magic Cap simulator. You will always break into the debugger
when a Try() catches an exception. This is done because Try() can catch any exception, so it
stops in the debugger to let you know that an exception was caught. This gives you a chance to see
what types of exceptions are falling into your Try() handler. Since Catch() only catches a specific
exception, it will not stop in the debugger.
This leads into an interesting discussion about when to use Catch() versus when to use Try().
If your code knows what kind of exceptions that can get thrown at a given time, you should always
use Catch(). You would use Try() if youÍre worried that some code you call might throw an exception,
but you don't know the kind of exception you might catch. Your exception handler would do what
clean up it can for your code, then pass the exception up with a Fail(). This is something that's
pointed out in the comments of the Exception sample, but not done. By not calling Fail(exception),
you're basically saying that your code has done all the clean up necessary after an exception. This
would never be the case since you didn't know the type of exception you might catch, which is why
you were using Try() in the first place. Calling Fail() passes the exception up to another handler
which might know how to deal with the exception. If it doesn't, the exception is passed up the
exception handler chain.
if (Catch(cannotAllocateMemory) != nilObject) {
Commit(); // Resize doesn't cause exception until Commit
Destroy(object);
return nilObject;
} else {
Resize(object, kIncreaseAmount);
Commit();
}
if (Catch(portIsInUse) != nilObject) {
Announce(iTelephoneLineInUse);
return;
}
if (Catch(cannotOpenPort) != nilObject) {
Announce(iCommHardwareError);
Commit(); // Remove the first exception handler
return;
}
if (Catch(notEnoughPowerForComms) != nilObject) {
Announce(iNotEnoughPowerForComms);
Commit(); Commit(); // Remove the first two exception handlers
return;
}
OpenPort(iModem);
// The operation was a success; remove all our exception handlers
Commit(); Commit(); Commit();
MyRoutine
{
ObjectID exception;
while(true) {
exception = Catch(cannotAllocateMemory);
if (exception == nilObject) {
NewTransientBuffer(50);
continue;
} else {
Announce(iSystemOutOfMemory);
return;
}
} //end while
}
MyRoutine
{
// A place to collect all the transient objects that this routine allocates.
// When transient memory runs out, destroying this list will destroy all the
// objects that have been allocated so far.
ObjectID myObjects = NewTransient(ObjectList_, nil);
NewBufferParameters parameters;
if (Catch(cannotAllocateMemory) != nilObject) {
// Destroy all allocated objects. Note that if the transient memory was allocated
// with NewTransientBuffer(), calling Destroy() on the ObjectList won't destroy
// the buffers. In this case, you'd need to explicitly call
// DestroyTransientBuffer() on each buffer in the ObjectList.
Destroy(myObjects);
// Now that some transient memory has freed up, put up the announcement.
Announce(iSystemOutOfMemory);
// You could call Fail(cannotAllocateMemory) here to pass up the exception to
// a higher level here.
return;
}
SetUpParameters(¶meters.header, 0);
parameters.initialSize = 50;
while (true) {
// System_TransientPercentUsed returns a Fixed value that represents the
// percent of transient memory that is currently allocated. For this example,
// if more than 90% of transient memory is consumed, throw an exception. The
// exception handler above will catch it and deal with it.
if (TransientPercentUsed(iSystem) > IntToFixed(90))
Fail(cannotAllocateMemory);
// Allocate a transient Buffer object and add it to the list of allocated objects.
// Note that if the preceeding if statement was not here, the NewTransient()
// would fail when the communicator was completely out of transient memory, but
// our exception handler would catch it, free up some memory, then (try to)
// present the out of memory announcement.
AddLast(myObjects, NewTransient(Buffer_, ¶meters.header));
}
// Calls to Catch() need to be balanced by calls to Commit(). Commit() tears down
// an exception handler that you set up with Catch(). Calling Fail() will cause
// the execution state to be reset to what is was when Catch() was called. This
// implicitly tears down the exception handler. You should call Commit() after
// you leave a section of code that might fail to remove the exception handler
// from the exception stack. In this example, this Commit() should never be
// encountered because the only way to leave the while loop is by failing. If,
// on the other hand, the loop terminated, you would need to take down the
// exception handler after the "critical" code section.
Commit();
}