Author: tack
Date: Sun Jan 20 19:22:53 2008
New Revision: 2995

Log:
Exception handlers now are expected to take three arguments instead of one:
exception type, exception value, and traceback.


Modified:
   trunk/base/src/notifier/async.py
   trunk/base/src/notifier/main.py
   trunk/base/src/notifier/nf_wrapper.py
   trunk/base/src/notifier/thread.py
   trunk/base/src/notifier/yieldfunc.py
   trunk/base/src/rpc.py
   trunk/base/test/asynctest.py

Modified: trunk/base/src/notifier/async.py
==============================================================================
--- trunk/base/src/notifier/async.py    (original)
+++ trunk/base/src/notifier/async.py    Sun Jan 20 19:22:53 2008
@@ -147,25 +147,21 @@
         self.exception = None
 
 
-    def throw(self, e):
+    def throw(self, type, value, tb):
         """
         This function should be called when the creating function is
         done because it raised an exception.
         """
         if self.exception.count() == 0:
-            if hasattr(e, '_exc_info'):
-                # Exception info set internally, so display traceback from 
that.
-                trace = ''.join(traceback.format_exception(*e._exc_info))
-            else:
-                # No traceback set explicitly
-                trace = '%s [no traceback available]' % repr(e)
+            # There is no handler, so dump the exception.
+            trace = ''.join(traceback.format_exception(type, value, tb))
+            log.error('*** Unhandled InProgress exception ***\n%s', trace)
 
-            log.error('*** InProgress exception not handled ***\n%s', trace)
         # store result
         self._finished = True
-        self._exception = e
+        self._exception = type, value, tb
         # emit signal
-        self.exception.emit_when_handled(e)
+        self.exception.emit_when_handled(type, value, tb)
         # cleanup
         self._callbacks = []
         self.exception = None
@@ -197,7 +193,9 @@
         if not self._finished:
             raise RuntimeError('operation not finished')
         if self._exception:
-            raise self._exception
+            type, value, tb = self._exception
+            # Special 3-argument form of raise; preserves traceback
+            raise type, value, tb
         return self._result
 
 

Modified: trunk/base/src/notifier/main.py
==============================================================================
--- trunk/base/src/notifier/main.py     (original)
+++ trunk/base/src/notifier/main.py     Sun Jan 20 19:22:53 2008
@@ -65,6 +65,7 @@
         notifier.dispatcher_remove(signals["step"].emit)
 
 signals = {
+    'exception': Signal(),
     'shutdown': Signal(),
     'step': Signal(changed_cb = _step_signal_changed),
 }
@@ -87,26 +88,39 @@
     """
     global _running
     _running = True
+    unhandled_exception = None
 
     set_as_mainthread()
-    try:
-        while True:
-            notifier.step()
-    except (KeyboardInterrupt, SystemExit):
+    while True:
         try:
-            # This looks stupid, I know that. The problem is that if we have
-            # a KeyboardInterrupt, that flag is still valid somewhere inside
-            # python. The next system call will fail because of that. Since we
-            # don't want a join of threads or similar fail, we use a very short
-            # sleep here. In most cases we won't sleep at all because this 
sleep
-            # fails. But after that everything is back to normal.
-            time.sleep(0.001)
-        except:
-            pass
-    except Exception, e:
-        log.exception('loop')
+            notifier.step()
+        except (KeyboardInterrupt, SystemExit):
+            try:
+                # This looks stupid, I know that. The problem is that if we 
have
+                # a KeyboardInterrupt, that flag is still valid somewhere 
inside
+                # python. The next system call will fail because of that. 
Since we
+                # don't want a join of threads or similar fail, we use a very 
short
+                # sleep here. In most cases we won't sleep at all because this 
sleep
+                # fails. But after that everything is back to normal.
+                time.sleep(0.001)
+            except:
+                pass
+            break
+        except Exception, e:
+            if signals['exception'].emit(*sys.exc_info()) != False:
+                # Either there are no global exception handlers, or none of
+                # them explicitly returned False to abort mainloop 
+                # termination.  So abort the main loop.
+                unhandled_exception = sys.exc_info()
+                break
+
     _running = False
     stop()
+    if unhandled_exception:
+        # We aborted the main loop due to an unhandled exception.  Now
+        # that we've cleaned up, we can reraise the exception.
+        type, value, tb = unhandled_exception
+        raise type, value, tb
 
 
 # Ensure stop() is called from main thread.

Modified: trunk/base/src/notifier/nf_wrapper.py
==============================================================================
--- trunk/base/src/notifier/nf_wrapper.py       (original)
+++ trunk/base/src/notifier/nf_wrapper.py       Sun Jan 20 19:22:53 2008
@@ -91,7 +91,7 @@
                 # If any of the exception handlers return True, then the
                 # object is not unregistered from the Notifier.  Otherwise
                 # ret = False and it will unregister.
-                ret = self.signals["exception"].emit(sys.exc_info()[1])
+                ret = self.signals["exception"].emit(*sys.exc_info())
         else:
             ret = super(NotifierCallback, self).__call__(*args, **kwargs)
         # If Notifier callbacks return False, they get unregistered.

Modified: trunk/base/src/notifier/thread.py
==============================================================================
--- trunk/base/src/notifier/thread.py   (original)
+++ trunk/base/src/notifier/thread.py   Sun Jan 20 19:22:53 2008
@@ -86,8 +86,8 @@
             self._sync_return = self._sync_return()
         self._wakeup()
 
-    def _set_exception(self, e):
-        self._sync_exception = e
+    def _set_exception(self, type, value, tb):
+        self._sync_exception = type, value, tb
         self._wakeup()
 
     def _wakeup(self):
@@ -116,7 +116,9 @@
             # the return value.
             self.lock.acquire()
             if self._sync_exception:
-                raise self._sync_exception
+                type, value, tb = self._sync_exception
+                # Special 3-argument form of raise; preserves traceback
+                raise type, value, tb
 
             return self._sync_return
 
@@ -144,8 +146,7 @@
         try:
             MainThreadCallback(self.finished, self._callback())()
         except Exception, e:
-            e._exc_info = sys.exc_info()
-            MainThreadCallback(self.throw, e)()
+            MainThreadCallback(self.throw, *sys.exc_info())()
         self._callback = None
 
 
@@ -280,6 +281,5 @@
         except Exception, e:
             log.exception('mainthread callback')
             # set exception in callback
-            e._exc_info = sys.exc_info()
             callback._set_exception(e)
     return True

Modified: trunk/base/src/notifier/yieldfunc.py
==============================================================================
--- trunk/base/src/notifier/yieldfunc.py        (original)
+++ trunk/base/src/notifier/yieldfunc.py        Sun Jan 20 19:22:53 2008
@@ -127,7 +127,7 @@
 # XXX This breaks existing code because now the exception is raised inside
 # XXX the yield call while this was safe until now, only get_result() could
 # XXX crash before that. After checking the code, _python25 should be set to
-# _python25 = sys.version.split()[0] > '2.4'
+_python25 = sys.version.split()[0] > '2.4'
 _python25 = False
 
 def _process(func, async=None):
@@ -136,8 +136,8 @@
     """
     if _python25 and async is not None:
         if async._exception:
-            e = async._exception
-            return func.throw(e.__class__, e)
+            print "THROWING TO GENERATOR"
+            return func.throw(*async._exception)
         return func.send(async._result)
     return func.next()
 
@@ -300,11 +300,10 @@
             result = None
         except Exception, e:
             # YieldFunction is done with exception
-            e._exc_info = sys.exc_info()
             self._timer.stop()
             self._async = None
             self._yield__function = None
-            self.throw(e)
+            self.throw(*sys.exc_info())
             return False
         # We have to stop the timer because we either have a result
         # or have to wait for an InProgress

Modified: trunk/base/src/rpc.py
==============================================================================
--- trunk/base/src/rpc.py       (original)
+++ trunk/base/src/rpc.py       Sun Jan 20 19:22:53 2008
@@ -89,6 +89,8 @@
 import sys
 import sha
 import time
+from new import classobj
+import traceback
 
 # kaa imports
 import kaa
@@ -99,6 +101,51 @@
 class ConnectError(Exception):
     pass
 
+
+def make_exception_class(name, bases, dict):
+    """
+    Class generator for RemoteException.  Creates RemoteException class
+    which derives the class of a particular Exception instance.
+    """
+    def create(exc, cmd, stack):
+        e = classobj(name, (exc.__class__,) + bases, dict)(*exc.args)
+        e._set_info(exc.__class__.__name__, cmd, stack)
+        return e
+
+    return create
+
+
+class RemoteException(object):
+    """
+    Raised when remote RPC calls raise exceptions.  Instances of this class
+    inherit the actual remote exception class, so this works:
+
+        try:
+            yield client.rpc('write_file')
+        except IOError, (errno, msg):
+            ...
+
+    When RemoteException instances are printed, they will also include the
+    traceback of the remote stack.
+    """
+    __metaclass__ = make_exception_class
+
+    def _set_info(self, exc_name, cmd, stack):
+        self._rpc_exc_name = exc_name
+        self._rpc_cmd = cmd
+        self._rpc_stack = stack
+
+    def __str__(self):
+        dump = ''.join(traceback.format_list(self._rpc_stack))
+        if self.message:
+            info = '%s: %s' % (self._rpc_exc_name, self.message)
+        else:
+            info = self._rpc_exc_name
+
+        return "Exception during RPC call '%s'; remote traceback follows:\n" % 
self._rpc_cmd + \
+               dump + info
+
+
 class Server(object):
     """
     RPC server class.
@@ -240,7 +287,7 @@
         payload = cPickle.dumps((cmd, args, kwargs), pickle.HIGHEST_PROTOCOL)
         self._send_packet(seq, packet_type, payload)
         # callback with error handler
-        self._rpc_in_progress[seq] = callback
+        self._rpc_in_progress[seq] = (callback, cmd)
         return callback
 
 
@@ -414,23 +461,24 @@
         return True
 
 
-    def _send_delayed_answer(self, payload, seq, packet_type):
+    def _send_answer(self, answer, seq):
         """
         Send delayed answer when callback returns InProgress.
         """
-        payload = cPickle.dumps(payload, pickle.HIGHEST_PROTOCOL)
-        self._send_packet(seq, packet_type, payload)
+        payload = cPickle.dumps(answer, pickle.HIGHEST_PROTOCOL)
+        self._send_packet(seq, 'RETN', payload)
 
 
-    def _send_delayed_exception(self, payload, seq, packet_type):
+    def _send_exception(self, type, value, tb, seq):
         """
         Send delayed exception when callback returns InProgress.
         """
+        stack = traceback.extract_tb(tb)
         try:
-            payload = cPickle.dumps(payload, pickle.HIGHEST_PROTOCOL)
+            payload = cPickle.dumps((value, stack), pickle.HIGHEST_PROTOCOL)
         except cPickle.UnpickleableError:
-            payload = cPickle.dumps(Exception(str(payload)))
-        self._send_packet(seq, packet_type, payload)
+            payload = cPickle.dumps((Exception(str(value)), stack), 
pickle.HIGHEST_PROTOCOL)
+        self._send_packet(seq, 'EXCP', payload)
 
 
     def _handle_packet_after_auth(self, seq, type, payload):
@@ -440,33 +488,33 @@
         """
         if type == 'CALL':
             # Remote function call, send answer
-            payload = cPickle.loads(payload)
-            function, args, kwargs = payload
+            function, args, kwargs = cPickle.loads(payload)
             try:
                 if self._callbacks[function]._kaa_rpc_param[0]:
                     args = [ self ] + list(args)
-                payload = self._callbacks[function](*args, **kwargs)
-                if isinstance(payload, kaa.InProgress):
-                    payload.connect(self._send_delayed_answer, seq, 'RETN')
-                    payload.exception.connect(self._send_delayed_exception, 
seq, 'EXCP')
-                    return True
-                packet_type = 'RETN'
+                result = self._callbacks[function](*args, **kwargs)
             except (SystemExit, KeyboardInterrupt):
                 sys.exit(0)
             except Exception, e:
-                log.exception('rpc call %s', function)
+                #log.exception('Exception in rpc function "%s"', function)
                 if not function in self._callbacks:
                     log.error(self._callbacks.keys())
-                packet_type = 'EXCP'
-                payload = e
-            payload = cPickle.dumps(payload, pickle.HIGHEST_PROTOCOL)
-            self._send_packet(seq, packet_type, payload)
+                type, value, tb = sys.exc_info()
+                self._send_exception(type, value, tb, seq)
+                return True
+
+            if isinstance(result, kaa.InProgress):
+                result.connect(self._send_answer, seq)
+                result.exception.connect(self._send_exception, seq)
+            else:
+                self._send_answer(result, seq)
+
             return True
 
         if type == 'RETN':
             # RPC return
             payload = cPickle.loads(payload)
-            callback = self._rpc_in_progress.get(seq)
+            callback, cmd = self._rpc_in_progress.get(seq)
             if callback is None:
                 return True
             del self._rpc_in_progress[seq]
@@ -475,12 +523,13 @@
 
         if type == 'EXCP':
             # Exception for remote call
-            error = cPickle.loads(payload)
-            callback = self._rpc_in_progress.get(seq)
+            exc_value, stack = cPickle.loads(payload)
+            callback, cmd = self._rpc_in_progress.get(seq)
             if callback is None:
                 return True
             del self._rpc_in_progress[seq]
-            callback.throw(error)
+            remote_exc = RemoteException(exc_value, cmd, stack)
+            callback.throw(remote_exc.__class__, remote_exc, None)
             return True
 
         log.error('unknown packet type %s', type)

Modified: trunk/base/test/asynctest.py
==============================================================================
--- trunk/base/test/asynctest.py        (original)
+++ trunk/base/test/asynctest.py        Sun Jan 20 19:22:53 2008
@@ -38,6 +38,10 @@
         time.sleep(0.1)
         return x
 
+    @kaa.rpc.expose('test6')
+    def test6(self, x):
+        raise ValueError
+
     @kaa.rpc.expose('shutdown')
     def shutdown(self):
         sys.exit(0)
@@ -184,7 +188,17 @@
         print 'bad rpc test failed'
     except:
         print 'bad rpc test ok'
-        pass
+
+    # rpc with remote exception
+    result = c.rpc('test6', 18)
+    yield result
+    try:
+        result()
+        print 'remote rpc exception test failed'
+    except ValueError, e:
+        print 'remote rpc exception test ok'
+        print "========= A traceback (for rpc) is expected below:"
+        print e
 
     # call rpc in thread
     x = thread2(c, 19)

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to