diff options
author | Jason Madden <jamadden@gmail.com> | 2017-03-21 10:31:24 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2017-03-21 12:45:07 -0500 |
commit | 71992b142ff980e16c6b670c99b170d14743b1ce (patch) | |
tree | 4be62be678b9831aa128da3b02160d344a664847 | |
parent | ebc7cd6f60bf5943658bf9f02c1d6068dfdb6331 (diff) |
Traceback uses iteration and not recursion.
This lets us properly create, pickle, and turn back into tracebacks
the results of exceeding the recursion limit.
Also, ``as_traceback`` is careful to not create reference cycles in the
traceback it returns by cleaning up the temporary variables.
Fixes #15.
-rw-r--r-- | AUTHORS.rst | 1 | ||||
-rw-r--r-- | CHANGELOG.rst | 6 | ||||
-rw-r--r-- | README.rst | 28 | ||||
-rw-r--r-- | src/tblib/__init__.py | 52 |
4 files changed, 71 insertions, 16 deletions
diff --git a/AUTHORS.rst b/AUTHORS.rst index 20d39ce..59052f3 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -6,3 +6,4 @@ Authors * Arcadiy Ivanov - https://github.com/arcivanov * Beckjake - https://github.com/beckjake * DRayX - https://github.com/DRayX +* Jason Madden - https://github.com/jamadden diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 422a3ce..ed419fe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= +1.3.1 (unreleased) +~~~~~~~~~~~~~~~~~~ + +* Fixed handling for tracebacks due to exceeding the recursion limit. + Fixes `#15 <https://github.com/ionelmc/python-tblib/issues/15>`_. + 1.3.0 (2016-03-08) ~~~~~~~~~~~~~~~~~~ @@ -86,8 +86,8 @@ Traceback serialization library. It allows you to: -* `Pickle <https://docs.python.org/3/library/pickle.html>`_ tracebacks and raise exceptions - with pickled tracebacks in different processes. This allows better error handling when running +* `Pickle <https://docs.python.org/3/library/pickle.html>`_ tracebacks and raise exceptions + with pickled tracebacks in different processes. This allows better error handling when running code over multiple processes (imagine multiprocessing, billiard, futures, celery etc). * Create traceback objects from strings (the ``from_string`` method). *No pickling is used*. * Serialize tracebacks to/from plain dicts (the ``from_dict`` and ``to_dict`` methods). *No pickling is used*. @@ -305,6 +305,28 @@ Or other import failures:: raise Exception("boom!") Exception: boom! +Or a traceback that's caused by exceeding the recursion limit (here we're +forcing the type and value to have consistency across platforms):: + + >>> def f(): f() + >>> try: + ... f() + ... except RuntimeError: + ... et, ev, tb = sys.exc_info() + ... tb = Traceback(tb) + ... + >>> reraise(RuntimeError, RuntimeError("maximum recursion depth exceeded"), tb.as_traceback()) + Traceback (most recent call last): + ... + File "<doctest README.rst[32]>", line 1, in f + def f(): f() + File "<doctest README.rst[32]>", line 1, in f + def f(): f() + File "<doctest README.rst[32]>", line 1, in f + def f(): f() + ... + RuntimeError: maximum recursion depth exceeded + Reference ~~~~~~~~~ @@ -351,7 +373,7 @@ json.JSONDecoder:: ... tb = Traceback(tb) ... tb_dict = tb.to_dict() ... pprint(tb_dict) - {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[37]>', + {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[...]>', 'co_name': '<module>'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, diff --git a/src/tblib/__init__.py b/src/tblib/__init__.py index 3a6abf6..4d0c7cf 100644 --- a/src/tblib/__init__.py +++ b/src/tblib/__init__.py @@ -53,21 +53,38 @@ class Frame(object): class Traceback(object): + + tb_next = None + def __init__(self, tb): self.tb_frame = Frame(tb.tb_frame) # noinspection SpellCheckingInspection self.tb_lineno = int(tb.tb_lineno) - if tb.tb_next is None: - self.tb_next = None - else: - self.tb_next = Traceback(tb.tb_next) + + # Build in place to avoid exceeding the recursion limit + tb = tb.tb_next + prev_traceback = self + cls = type(self) + while tb is not None: + traceback = object.__new__(cls) + traceback.tb_frame = Frame(tb.tb_frame) + traceback.tb_lineno = int(tb.tb_lineno) + prev_traceback.tb_next = traceback + prev_traceback = traceback + tb = tb.tb_next def as_traceback(self): if tproxy: return tproxy(TracebackType, self.__tproxy_handler) - elif tb_set_next: - f_code = self.tb_frame.f_code - code = compile('\n' * (self.tb_lineno - 1) + 'raise __traceback_maker', self.tb_frame.f_code.co_filename, 'exec') + if not tb_set_next: + raise RuntimeError("Cannot re-create traceback !") + + current = self + top_tb = None + tb = None + while current: + f_code = current.tb_frame.f_code + code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec') if PY3: code = CodeType( 0, code.co_kwonlyargcount, @@ -87,13 +104,22 @@ class Traceback(object): # noinspection PyBroadException try: - exec(code, self.tb_frame.f_globals, {}) + exec(code, current.tb_frame.f_globals, {}) except: - tb = sys.exc_info()[2].tb_next - tb_set_next(tb, self.tb_next and self.tb_next.as_traceback()) - return tb - else: - raise RuntimeError("Cannot re-create traceback !") + next_tb = sys.exc_info()[2].tb_next + if top_tb is None: + top_tb = next_tb + if tb is not None: + tb_set_next(tb, next_tb) + tb = next_tb + del next_tb + + current = current.tb_next + try: + return top_tb + finally: + del top_tb + del tb # noinspection SpellCheckingInspection def __tproxy_handler(self, operation, *args, **kwargs): |