From aa61c29efb04526e99998dc82545540d07d8368f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 4 Apr 2016 22:02:42 +0800 Subject: [PATCH] transfer Python builtin exceptions over pc_rpc and master/worker --- artiq/frontend/artiq_rpctool.py | 8 ++-- artiq/master/worker.py | 13 +++--- artiq/master/worker_impl.py | 18 +++----- artiq/protocols/packed_exceptions.py | 42 +++++++++++++++++++ artiq/protocols/pc_rpc.py | 26 ++++-------- artiq/test/test_pc_rpc.py | 4 +- .../repository/remote_exec_processing.py | 3 +- 7 files changed, 70 insertions(+), 44 deletions(-) create mode 100644 artiq/protocols/packed_exceptions.py diff --git a/artiq/frontend/artiq_rpctool.py b/artiq/frontend/artiq_rpctool.py index 4c63f7428..066dd9b81 100755 --- a/artiq/frontend/artiq_rpctool.py +++ b/artiq/frontend/artiq_rpctool.py @@ -8,7 +8,7 @@ import numpy as np # Needed to use numpy in RPC call arguments on cmd line import readline # This makes input() nicer import pprint -from artiq.protocols.pc_rpc import AutoTarget, Client, RemoteError +from artiq.protocols.pc_rpc import AutoTarget, Client def get_argparser(): @@ -101,9 +101,11 @@ def interactive(remote): try: ret = eval(cmd, {}, RemoteDict()) except Exception as e: - if isinstance(e, RemoteError): + if hasattr(e, "parent_traceback"): print("Remote exception:") - print(str(e)) + print(traceback.format_exception_only(type(e), e)[0].rstrip()) + for l in e.parent_traceback: + print(l.rstrip()) else: traceback.print_exc() else: diff --git a/artiq/master/worker.py b/artiq/master/worker.py index 3ac6278ac..396059b1a 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -3,11 +3,11 @@ import os import asyncio import logging import subprocess -import traceback import time from artiq.protocols import pipe_ipc, pyon from artiq.protocols.logging import LogParser +from artiq.protocols.packed_exceptions import current_exc_packed from artiq.tools import asyncio_wait_or_cancel @@ -216,12 +216,11 @@ class Worker: try: data = func(*obj["args"], **obj["kwargs"]) reply = {"status": "ok", "data": data} - except Exception as e: - reply = {"status": "failed", - "exception": traceback.format_exception_only( - type(e), e)[0][:-1], - "message": str(e), - "traceback": traceback.format_tb(e.__traceback__)} + except: + reply = { + "status": "failed", + "exception": current_exc_packed() + } await self.io_lock.acquire() try: await self._send(reply) diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 722a1c9a8..6bb1aaf4d 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -7,6 +7,7 @@ from collections import OrderedDict import artiq from artiq.protocols import pipe_ipc, pyon +from artiq.protocols.packed_exceptions import raise_packed_exc from artiq.tools import multiline_log_config, file_import from artiq.master.worker_db import DeviceManager, DatasetManager, get_hdf5_output from artiq.language.environment import is_experiment @@ -27,11 +28,7 @@ def put_object(obj): ipc.write((ds + "\n").encode()) -class ParentActionError(Exception): - pass - - -def make_parent_action(action, exception=None): +def make_parent_action(action): def parent_action(*args, **kwargs): request = {"action": action, "args": args, "kwargs": kwargs} put_object(request) @@ -44,22 +41,17 @@ def make_parent_action(action, exception=None): if reply["status"] == "ok": return reply["data"] else: - if exception is None: - exn = ParentActionError(reply["exception"]) - else: - exn = exception(reply["message"]) - exn.parent_traceback = reply["traceback"] - raise exn + raise_packed_exc(reply["exception"]) return parent_action class ParentDeviceDB: get_device_db = make_parent_action("get_device_db") - get = make_parent_action("get_device", KeyError) + get = make_parent_action("get_device") class ParentDatasetDB: - get = make_parent_action("get_dataset", KeyError) + get = make_parent_action("get_dataset") update = make_parent_action("update_dataset") diff --git a/artiq/protocols/packed_exceptions.py b/artiq/protocols/packed_exceptions.py new file mode 100644 index 000000000..2c453bf80 --- /dev/null +++ b/artiq/protocols/packed_exceptions.py @@ -0,0 +1,42 @@ +import inspect +import builtins +import traceback +import sys + + +__all__ = ["GenericRemoteException", "current_exc_packed", "raise_packed_exc"] + + +class GenericRemoteException(Exception): + pass + + +builtin_exceptions = {v: k for k, v in builtins.__dict__.items() + if inspect.isclass(v) and issubclass(v, BaseException)} + + +def current_exc_packed(): + exc_class, exc, exc_tb = sys.exc_info() + if exc_class in builtin_exceptions: + return { + "class": builtin_exceptions[exc_class], + "message": str(exc), + "traceback": traceback.format_tb(exc_tb) + } + else: + message = traceback.format_exception_only(exc_class, exc)[0].rstrip() + return { + "class": "GenericRemoteException", + "message": message, + "traceback": traceback.format_tb(exc_tb) + } + + +def raise_packed_exc(pack): + if pack["class"] == "GenericRemoteException": + cls = GenericRemoteException + else: + cls = getattr(builtins, pack["class"]) + exc = cls(pack["message"]) + exc.parent_traceback = pack["traceback"] + raise exc diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index 5be798c6d..1ba4b17c4 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -13,7 +13,6 @@ client's list. import socket import asyncio -import traceback import threading import time import logging @@ -22,6 +21,7 @@ from operator import itemgetter from artiq.protocols import pyon from artiq.protocols.asyncio_server import AsyncioServer as _AsyncioServer +from artiq.protocols.packed_exceptions import * logger = logging.getLogger(__name__) @@ -33,12 +33,6 @@ class AutoTarget: pass -class RemoteError(Exception): - """Raised when a RPC failed or raised an exception on the remote (server) - side.""" - pass - - class IncompatibleServer(Exception): """Raised by the client when attempting to connect to a server that does not have the expected target.""" @@ -163,7 +157,7 @@ class Client: if obj["status"] == "ok": return obj["ret"] elif obj["status"] == "failed": - raise RemoteError(obj["message"]) + raise_packed_exc(obj["exception"]) else: raise ValueError @@ -267,7 +261,7 @@ class AsyncioClient: if obj["status"] == "ok": return obj["ret"] elif obj["status"] == "failed": - raise RemoteError(obj["message"]) + raise_packed_exc(obj["exception"]) else: raise ValueError finally: @@ -395,7 +389,7 @@ class BestEffortClient: if obj["status"] == "ok": return obj["ret"] elif obj["status"] == "failed": - raise RemoteError(obj["message"]) + raise_packed_exc(obj["exception"]) else: raise ValueError @@ -524,13 +518,11 @@ class Server(_AsyncioServer): .format(obj["action"])) except asyncio.CancelledError: raise - except Exception as exc: - short_exc_info = type(exc).__name__ - exc_str = str(exc) - if exc_str: - short_exc_info += ": " + exc_str.splitlines()[0] - return {"status": "failed", - "message": short_exc_info + "\n" + traceback.format_exc()} + except: + return { + "status": "failed", + "exception": current_exc_packed() + } finally: if self._noparallel is not None: self._noparallel.release() diff --git a/artiq/test/test_pc_rpc.py b/artiq/test/test_pc_rpc.py index 370b21188..52271af89 100644 --- a/artiq/test/test_pc_rpc.py +++ b/artiq/test/test_pc_rpc.py @@ -45,7 +45,7 @@ class RPCCase(unittest.TestCase): self.assertEqual(test_object, test_object_back) test_object_back = remote.async_echo(test_object) self.assertEqual(test_object, test_object_back) - with self.assertRaises(pc_rpc.RemoteError): + with self.assertRaises(AttributeError): remote.non_existing_method() remote.terminate() finally: @@ -72,7 +72,7 @@ class RPCCase(unittest.TestCase): self.assertEqual(test_object, test_object_back) test_object_back = await remote.async_echo(test_object) self.assertEqual(test_object, test_object_back) - with self.assertRaises(pc_rpc.RemoteError): + with self.assertRaises(AttributeError): await remote.non_existing_method() await remote.terminate() finally: diff --git a/examples/master/repository/remote_exec_processing.py b/examples/master/repository/remote_exec_processing.py index 03a62265b..6a3d865d7 100644 --- a/examples/master/repository/remote_exec_processing.py +++ b/examples/master/repository/remote_exec_processing.py @@ -39,10 +39,9 @@ def get_and_fit(): if "dataset_db" in globals(): logger.info("using dataset DB for Gaussian fit guess") def get_dataset(name, default): - from artiq.protocols import pc_rpc try: return dataset_db.get(name) - except (KeyError, pc_rpc.RemoteError): # TODO: serializable exceptions + except KeyError: return default else: logger.info("using defaults for Gaussian fit guess")