summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2017-03-21 10:31:24 -0500
committerJason Madden <jamadden@gmail.com>2017-03-21 12:45:07 -0500
commit71992b142ff980e16c6b670c99b170d14743b1ce (patch)
tree4be62be678b9831aa128da3b02160d344a664847
parentebc7cd6f60bf5943658bf9f02c1d6068dfdb6331 (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.rst1
-rw-r--r--CHANGELOG.rst6
-rw-r--r--README.rst28
-rw-r--r--src/tblib/__init__.py52
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)
~~~~~~~~~~~~~~~~~~
diff --git a/README.rst b/README.rst
index 63c37b9..135c722 100644
--- a/README.rst
+++ b/README.rst
@@ -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):