[IMP] xmlrpc: somewhat streamlined the exception types/

exception handling code.
OpenERPDispatcherException which was simply wrapping an exception together with
an exc_info is removed.

bzr revid: vmt@openerp.com-20110927102246-87ibftawukc2d8lc
This commit is contained in:
Vo Minh Thu 2011-09-27 12:22:46 +02:00
parent e8dc0edbaa
commit 08cfadaff0
5 changed files with 57 additions and 41 deletions

View File

@ -19,6 +19,14 @@
# #
############################################################################## ##############################################################################
""" OpenERP core exceptions.
This module defines a few exception types. Those types are understood by the
RPC layer. Any other exception type bubbling until the RPC layer will be
treated as a 'Server error'.
"""
class Warning(Exception): class Warning(Exception):
pass pass
@ -32,5 +40,19 @@ class AccessDenied(Exception):
class AccessError(Exception): class AccessError(Exception):
""" Access rights error. """ """ Access rights error. """
class DeferredException(Exception):
""" Exception object holding a traceback for asynchronous reporting.
Some RPC calls (database creation and report generation) happen with
an initial request followed by multiple, polling requests. This class
is used to store the possible exception occuring in the thread serving
the first request, and is then sent to a polling request.
('Traceback' is misleading, this is really a exc_info() triple.)
"""
def __init__(self, msg, tb):
self.message = msg
self.traceback = tb
self.args = (msg, tb)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -370,11 +370,6 @@ class Server:
def _close_socket(self): def _close_socket(self):
close_socket(self.socket) close_socket(self.socket)
class OpenERPDispatcherException(Exception):
def __init__(self, exception, traceback):
self.exception = exception
self.traceback = traceback
def replace_request_password(args): def replace_request_password(args):
# password is always 3rd argument in a request, we replace it in RPC logs # password is always 3rd argument in a request, we replace it in RPC logs
# so it's easier to forward logs for diagnostics/debugging purposes... # so it's easier to forward logs for diagnostics/debugging purposes...
@ -421,13 +416,18 @@ def dispatch_rpc(service_name, method, params):
raise raise
except openerp.exceptions.Warning: except openerp.exceptions.Warning:
raise raise
except openerp.exceptions.DeferredException, e:
_log('exception', tools.exception_to_unicode(e))
post_mortem(e.traceback)
raise
except Exception, e: except Exception, e:
_log('exception', tools.exception_to_unicode(e)) _log('exception', tools.exception_to_unicode(e))
tb = getattr(e, 'traceback', sys.exc_info()) post_mortem(sys.exc_info())
tb_s = "".join(traceback.format_exception(*tb)) raise
if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType):
import pdb def post_mortem(info):
pdb.post_mortem(tb[2]) if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType):
raise OpenERPDispatcherException(e, tb_s) import pdb
pdb.post_mortem(info[2])
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -65,21 +65,13 @@ class TinySocketClientThread(threading.Thread):
except socket.timeout: except socket.timeout:
#terminate this channel because other endpoint is gone #terminate this channel because other endpoint is gone
break break
except netsvc.OpenERPDispatcherException, e:
try:
new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
logging.getLogger('web-services').debug("netrpc: rpc-dispatching exception", exc_info=True)
ts.mysend(new_e, exception=True, traceback=e.traceback)
except Exception:
#terminate this channel if we can't properly send back the error
logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
break
except Exception, e: except Exception, e:
try: try:
new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling
tb = getattr(e, 'traceback', sys.exc_info()) tb = getattr(e, 'traceback', sys.exc_info())
tb_s = "".join(traceback.format_exception(*tb)) tb_s = "".join(traceback.format_exception(*tb))
logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True) logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
ts.mysend(e, exception=True, traceback=tb_s) ts.mysend(new_e, exception=True, traceback=tb_s)
break break
except Exception, ex: except Exception, ex:
#terminate this channel if we can't properly send back the error #terminate this channel if we can't properly send back the error

View File

@ -162,7 +162,7 @@ class db(netsvc.ExportService):
self.actions.pop(id) self.actions.pop(id)
return (1.0, users) return (1.0, users)
else: else:
e = self.actions[id]['exception'] e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
self.actions.pop(id) self.actions.pop(id)
raise Exception, e raise Exception, e
@ -634,12 +634,6 @@ class wizard(netsvc.ExportService):
# False -> True # False -> True
# #
class ExceptionWithTraceback(Exception):
def __init__(self, msg, tb):
self.message = msg
self.traceback = tb
self.args = (msg, tb)
class report_spool(netsvc.ExportService): class report_spool(netsvc.ExportService):
def __init__(self, name='report'): def __init__(self, name='report'):
netsvc.ExportService.__init__(self, name) netsvc.ExportService.__init__(self, name)
@ -678,7 +672,7 @@ class report_spool(netsvc.ExportService):
(result, format) = obj.create(cr, uid, ids, datas, context) (result, format) = obj.create(cr, uid, ids, datas, context)
if not result: if not result:
tb = sys.exc_info() tb = sys.exc_info()
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb) self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
self._reports[id]['result'] = result self._reports[id]['result'] = result
self._reports[id]['format'] = format self._reports[id]['format'] = format
self._reports[id]['state'] = True self._reports[id]['state'] = True
@ -690,9 +684,9 @@ class report_spool(netsvc.ExportService):
logger.notifyChannel('web-services', netsvc.LOG_ERROR, logger.notifyChannel('web-services', netsvc.LOG_ERROR,
'Exception: %s\n%s' % (str(exception), tb_s)) 'Exception: %s\n%s' % (str(exception), tb_s))
if hasattr(exception, 'name') and hasattr(exception, 'value'): if hasattr(exception, 'name') and hasattr(exception, 'value'):
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value)) self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
else: else:
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb) self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
self._reports[id]['state'] = True self._reports[id]['state'] = True
cr.commit() cr.commit()
cr.close() cr.close()
@ -721,7 +715,7 @@ class report_spool(netsvc.ExportService):
(result, format) = obj.create(cr, uid, ids, datas, context) (result, format) = obj.create(cr, uid, ids, datas, context)
if not result: if not result:
tb = sys.exc_info() tb = sys.exc_info()
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb) self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
self._reports[id]['result'] = result self._reports[id]['result'] = result
self._reports[id]['format'] = format self._reports[id]['format'] = format
self._reports[id]['state'] = True self._reports[id]['state'] = True
@ -733,9 +727,9 @@ class report_spool(netsvc.ExportService):
logger.notifyChannel('web-services', netsvc.LOG_ERROR, logger.notifyChannel('web-services', netsvc.LOG_ERROR,
'Exception: %s\n%s' % (str(exception), tb_s)) 'Exception: %s\n%s' % (str(exception), tb_s))
if hasattr(exception, 'name') and hasattr(exception, 'value'): if hasattr(exception, 'name') and hasattr(exception, 'value'):
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value)) self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
else: else:
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb) self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
self._reports[id]['state'] = True self._reports[id]['state'] = True
cr.commit() cr.commit()
cr.close() cr.close()

View File

@ -36,6 +36,7 @@ import signal
import sys import sys
import threading import threading
import time import time
import traceback
import openerp import openerp
import openerp.modules import openerp.modules
@ -46,7 +47,8 @@ import service.websrv_lib as websrv_lib
# constants are also defined client-side and must remain in sync. # constants are also defined client-side and must remain in sync.
# User code must use the exceptions defined in ``openerp.exceptions`` (not # User code must use the exceptions defined in ``openerp.exceptions`` (not
# create directly ``xmlrpclib.Fault`` objects). # create directly ``xmlrpclib.Fault`` objects).
XML_RPC_FAULT_CODE_APPLICATION_ERROR = 2 XML_RPC_FAULT_CODE_APPLICATION_ERROR = 1
XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR = 2
XML_RPC_FAULT_CODE_ACCESS_DENIED = 3 XML_RPC_FAULT_CODE_ACCESS_DENIED = 3
XML_RPC_FAULT_CODE_WARNING = 4 XML_RPC_FAULT_CODE_WARNING = 4
XML_RPC_FAULT_CODE_ACCESS_ERROR = 5 XML_RPC_FAULT_CODE_ACCESS_ERROR = 5
@ -77,13 +79,19 @@ def xmlrpc_return(start_response, service, method, params):
except openerp.exceptions.AccessDenied, e: except openerp.exceptions.AccessDenied, e:
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e)) fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e))
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
except openerp.netsvc.OpenERPDispatcherException, e: except openerp.exceptions.DeferredException, e:
# TODO collapse this case with the next one. info = e.traceback
fault = xmlrpclib.Fault(2, openerp.tools.exception_to_unicode(e.exception) + '\n' + e.traceback) # Which one is the best ?
formatted_info = "".join(traceback.format_exception(*info))
#formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR, formatted_info)
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
except: except Exception, e:
exc_type, exc_value, exc_tb = sys.exc_info() info = sys.exc_info()
fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)) # Which one is the best ?
formatted_info = "".join(traceback.format_exception(*info))
#formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
response = xmlrpclib.dumps(fault, allow_none=None, encoding=None) response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))]) start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
return [response] return [response]