Statically Typed

because Hindley-Milner rocks

Swig, Python, and C++ exceptions, part 5


What a wonderful time I’m having with Swig, cross-language polymorphism, and C++ exception catching.  My last post indicated how I would proceed with exception handling and I attempted to work that into the mix.  I wish I could say that it performed adequately but it didn’t.  Here’s the code I set up:

class FooDecorator{
public:

   FooDecorator(Foo *_foo) : m_Foo(_foo){}
   virtual ~FooDecorator(){}

   void stopIt(){ return m_Foo->throw(); }
   int getVal() { return m_Foo->getInt(); }
   int experiment(){ throw exception(); return -1; }

private:

   Foo *m_Foo;
};

Then I changed the *.i file to reflect new Swig preprocessor instructions.  I made sure to specifically target only FooDecorator for the exception handling while retaining the Python -> C++ exception capabilities of Foo:

%feature("director") Foo;
%feature("director:exception") Foo {
   if( $error != NULL ){
      throw Swig::DirectorMethodException();
}
%except FooDecorator {
   try{
      $action
   }
   catch(...){
      PyErr_SetString( PyExc_RuntimeError, "Got it!" );
   }
}

The results were surprising.  First of all, when an exception was raised from within the Foo object the exception seemed to pass through to the Swig logic embedded into the FooDecorator.  However, when I called the FooDecorator::experiment method all hell broke loose.  The function half the time would return a corrupted integer value and the other half an exception.  Upon termination of the Python environment the missing exceptions would reappear during garbage collection, hosing anything else that needed to be collected but hadn’t yet.

In layman’s terms this meant that calling the method left my program in a state reminiscent of a ticking time bomb, one garbage collection away from blowing up.  This left only one more option, the removal of the %exception clause.  To be able to pass C++ exceptions up to the Python stack I’d need a new object.  I would mirror my workaround for the Python -> C++ exception fiasco by creating a Python exception factory then I would incorporate that class into my FooDecorator.

struct PyExceptionFactory{
    PyExceptionFactory(){}
    virtual void raiseRuntimeError() =0;
    virtual void raiseValueError() =0;
};

class FooDecorator{
public:

   FooDecorator(Foo *_foo, PyExceptionFactory *_factory)
    : m_Foo(_foo),
      m_Factory(_factory){}
   virtual ~FooDecorator(){}

   void experiment(){
      try{
         m_Foo->throw();
      }
      catch(...){
         m_Factory->raiseRuntimeError();
      }
   }

private:

   Foo                *m_Foo;
   PyExceptionFactory *m_Factory;
};

Adding a director to the *.i file:

%feature("director") PyExceptionFactory;

and with the Python implementation as so

class ErrorFactory(PyExceptionFactory):
   def __init__(self):
      PyExceptionFactory.__init__(self)
   def raiseRuntimeError(self):
      raise RuntimeError
   def raiseValueError(self):
      raise ValueError

This next part will stun you.  Better yet, play Devil’s advocate.  Before I tell you what happened think about it.  What is left of the Swig exception preprocessor directives and how could they conspire to screw it all up?  It’s the %feature(“director:exception”) clause.

Despite the fact that I told Swig only to inject that logic into the Foo class, it still managed to intercept the Python error on the Python stack and pass it down to the C++ code.  However, without the %exception logic to intercede the exception the Python environment was hosed.  For a play by play recount of how it happened:

  1. Foo::throw creates an exception
  2. FooDecorator::experiment‘s try-catch block catches it and makes a call to PyExceptionFactory::raiseRuntimeError
  3. The Python ErrorFactory::raiseRuntimeError raises a Python RuntimeError on the Python stack
  4. The Swig code catches that error and reconverts to back to a C++ Swig::DecoratorMethodException.
  5. That exception is passed back to the C++ code outside of the try-catch blocks of FooDecorator::experiment
  6. It all comes crumbling down

Not pretty.  Looks like I’ll be getting rid of all the Swig exception logic and begrudgingly asking my coworkers to wrap their Python code in a true Python decorator.  A decorator which makes calls to the C++ exception factory object every time it catches an exception.  Ugh!

You know what this experience has taught me?  To respect all the people whose hard work has gone into Swig.  There’s a lot of challenges they’ve faced and a lot of interesting problems they’ve had to solve.  This illustrates just one of the places where they couldn’t think of everything.  Perhaps it means I need to donate my time to helping them realize their goals.  Who am I kidding?  I have too many half-baked side projects here I need to finish first.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on June 8, 2010 by in C++, Python.
%d bloggers like this: