From 08cfadaff074703cf480051a1351f851fcaf477f Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 27 Sep 2011 12:22:46 +0200 Subject: [PATCH] [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 --- openerp/exceptions.py | 22 ++++++++++++++++++++++ openerp/netsvc.py | 22 +++++++++++----------- openerp/service/netrpc_server.py | 12 ++---------- openerp/service/web_services.py | 20 +++++++------------- openerp/wsgi.py | 22 +++++++++++++++------- 5 files changed, 57 insertions(+), 41 deletions(-) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index 944e34fb3ef..50571306d29 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -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): pass @@ -32,5 +40,19 @@ class AccessDenied(Exception): class AccessError(Exception): """ 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: diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 7f81228de8c..7e62687e335 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -370,11 +370,6 @@ class Server: def _close_socket(self): close_socket(self.socket) -class OpenERPDispatcherException(Exception): - def __init__(self, exception, traceback): - self.exception = exception - self.traceback = traceback - def replace_request_password(args): # 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... @@ -421,13 +416,18 @@ def dispatch_rpc(service_name, method, params): raise except openerp.exceptions.Warning: raise + except openerp.exceptions.DeferredException, e: + _log('exception', tools.exception_to_unicode(e)) + post_mortem(e.traceback) + raise except Exception, e: _log('exception', tools.exception_to_unicode(e)) - tb = getattr(e, 'traceback', sys.exc_info()) - tb_s = "".join(traceback.format_exception(*tb)) - if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType): - import pdb - pdb.post_mortem(tb[2]) - raise OpenERPDispatcherException(e, tb_s) + post_mortem(sys.exc_info()) + raise + +def post_mortem(info): + if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType): + import pdb + pdb.post_mortem(info[2]) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index 3667b38c0b8..adebca63c7f 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -65,21 +65,13 @@ class TinySocketClientThread(threading.Thread): except socket.timeout: #terminate this channel because other endpoint is gone 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: try: + new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling tb = getattr(e, 'traceback', sys.exc_info()) tb_s = "".join(traceback.format_exception(*tb)) 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 except Exception, ex: #terminate this channel if we can't properly send back the error diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index a3bdb48a953..a6be9827c52 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -162,7 +162,7 @@ class db(netsvc.ExportService): self.actions.pop(id) return (1.0, users) 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) raise Exception, e @@ -634,12 +634,6 @@ class wizard(netsvc.ExportService): # 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): def __init__(self, name='report'): netsvc.ExportService.__init__(self, name) @@ -678,7 +672,7 @@ class report_spool(netsvc.ExportService): (result, format) = obj.create(cr, uid, ids, datas, context) if not result: 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]['format'] = format self._reports[id]['state'] = True @@ -690,9 +684,9 @@ class report_spool(netsvc.ExportService): logger.notifyChannel('web-services', netsvc.LOG_ERROR, 'Exception: %s\n%s' % (str(exception), tb_s)) 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: - 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 cr.commit() cr.close() @@ -721,7 +715,7 @@ class report_spool(netsvc.ExportService): (result, format) = obj.create(cr, uid, ids, datas, context) if not result: 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]['format'] = format self._reports[id]['state'] = True @@ -733,9 +727,9 @@ class report_spool(netsvc.ExportService): logger.notifyChannel('web-services', netsvc.LOG_ERROR, 'Exception: %s\n%s' % (str(exception), tb_s)) 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: - 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 cr.commit() cr.close() diff --git a/openerp/wsgi.py b/openerp/wsgi.py index 984b049d902..8db39a780c7 100644 --- a/openerp/wsgi.py +++ b/openerp/wsgi.py @@ -36,6 +36,7 @@ import signal import sys import threading import time +import traceback import openerp 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. # User code must use the exceptions defined in ``openerp.exceptions`` (not # 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_WARNING = 4 XML_RPC_FAULT_CODE_ACCESS_ERROR = 5 @@ -77,13 +79,19 @@ def xmlrpc_return(start_response, service, method, params): except openerp.exceptions.AccessDenied, e: fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e)) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) - except openerp.netsvc.OpenERPDispatcherException, e: - # TODO collapse this case with the next one. - fault = xmlrpclib.Fault(2, openerp.tools.exception_to_unicode(e.exception) + '\n' + e.traceback) + except openerp.exceptions.DeferredException, e: + info = 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) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)) + except Exception, e: + info = sys.exc_info() + # 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) start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))]) return [response]