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