Exceptions


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?

if (Catch(cannotAllocateMemory) != nilObject) {
   Commit(); // Resize doesn't cause exception until Commit
   Destroy(object);
   return nilObject;
} else {
   Resize(object, kIncreaseAmount);
   Commit();
}


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:

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();


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?

MyRoutine
{
   ObjectID exception;

   while(true) {
      exception = Catch(cannotAllocateMemory);
      if (exception == nilObject) {
         NewTransientBuffer(50);
         continue;
      } else {
         Announce(iSystemOutOfMemory);
         return;
      }
   } //end while
}


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.

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();
}


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 an exception occurs inside my routine but I haven't set up an exception handler to catch exceptions, will the objects I allocated be destroyed?


No. If you create objects and then an exception occurs that you don't catch, your objects will remain in memory. If the exception remains uncaught, Magic Cap resets and cleans out memory. If your objects were transient, they will be thrown away. Persistent objects would remain in memory until the next full garbage collection is performed.

If you created objects that you want to throw away when unexpected exceptions occur, you need to set up a Try() exception handler:

Method void
MyClass_MyMethod(ObjectID self, ...)
{
   ObjectID temporaryObject;
   ObjectID permanentObject;
   ObjectID exception;

   temporaryObject = nilObject;
   permanentObject = nilObject;

   // Normally, you should use Catch() to catch particular exceptions, but
   // in this case, we just want a chance to clean up after any exception,
   // so we can use Try(), then Fail() to pass the exception on.
   if ((exception = Try()) != nilObject) {

   // Some exception occurred. Look at the exception to determine which objects
   // to throw away. Throw away the temporary object when exceptionType1 occurs;
   // throw away the permanent object when exceptionType2 occurs; throw away
   // both objects when exceptionType3 occurs.
      if ((exception == exceptionType1) ||
          (exception == exceptionType3) &&
          HasObject(temporaryObject))
         Destroy(temporaryObject);

      if ((exception == exceptionType2) ||
          (exception == exceptionType3) &&
          HasObject(permanentObject))
         Destroy(permanentObject);

   // Pass the exception up like a good citizen.
      Fail(exception);
   }

   temporaryObject = NewNear(TransientClass_, kPackageTransient);
   permanentObject = NewNear(PersistentClass_, kPackagePersistent);

   // Continue on, but because the exception handler is still in place,
   // any code that causes an exception in this routine will cause
   // execution to reset inside the handler.
   ...

   // We're done, so take down the handler.
   Commit();
}


Is it possible to throw exceptions across package boundaries?


Yes, in a restricted sort of way. If one package fails with a system defined exception number, another package will be able to catch that exception, via Catch() or Try(). The same is true of package defined exception numbers, but you'd need to be careful if two packages use the same exception numbers. An exception handler wouldn't be able to distinguish exceptions from the two packages.

You won't be able to throw object exceptions from one package to another and use CatchByClass() to catch them. If you wanted to do this, you would probably try to import the exception class from one class, then set up a CatchByClass() with the class import. The problem is that the exception dispatcher works from the failing package's context, so the Implements() check fails. If you really really wanted to do this, you could set something up with a Try() handler instead:

importedExceptionClass = Import(foreignContext, foreignClassNumber);
if ((exception = Try()) != nilObject) {
   // See if this exception inherits from the imported exception class.
   // If not, pass the exception up to the next handler.
   // Normally, you'd Fail() for all Try() handlers, but because we're using
   // a Try() like a CatchByClass() in this case, just do the clean up without
   // failing.
   if (!Implements(exception, importedExceptionClass))
      Fail(exception);

   // If this exception does inherit from the imported exception class,
   // do the necessary clean up.
} else {
  // Code that might cause an exception

  Commit();
}