New issue 3120: PyErr_Restore does not restore traceback https://bitbucket.org/pypy/pypy/issues/3120/pyerr_restore-does-not-restore-traceback
Kirill Smelkov: Hello up there. I again hit PyPy vs CPython incomatibility while moving parts of Pygolang to Cython. Please consider the following minimal example: \(mymod.pyx\) ```python # cython: language_level=2 cdef extern from "Python.h": ctypedef struct PyObject PyObject *PyObject_CallFunction(PyObject *f, const char *fmt, ...) void PyErr_Fetch(PyObject **pexc_type, PyObject **pexc_value, PyObject **pexc_tb) void PyErr_Restore(PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb) void Py_XINCREF(PyObject*) # PyExc wraps information about Python exception cdef class PyExc: # retrieved by PyErr_Fetch - keep 1 reference to each cdef PyObject *exc_type cdef PyObject *exc_value cdef PyObject *exc_tb # call_pyfunc calls f and return PyExc describing exception state after f call. # f must raise an exception. def call_pyfunc(f): cdef PyExc pyexc = PyExc() cdef PyObject *ret ret = PyObject_CallFunction(<PyObject*>f, NULL) if ret != NULL: raise AssertionError('f must raise an exception') PyErr_Fetch(&pyexc.exc_type, &pyexc.exc_value, &pyexc.exc_tb) return pyexc # reraise_pyexc reraises pyexc including its original traceback. def reraise_pyexc(PyExc pyexc not None): _pyexc_reraise(pyexc) cdef void _pyexc_reraise(PyExc pyexc) except *: # PyErr_Restore takes 1 reference to restored objects. # We want to keep pyerr itself alive and valid. Py_XINCREF(pyexc.exc_type); Py_XINCREF(pyexc.exc_value); Py_XINCREF(pyexc.exc_tb); PyErr_Restore(pyexc.exc_type, pyexc.exc_value, pyexc.exc_tb) ``` \(mytest.py\) ```python #!/usr/bin/env python import mymod def f(): g() def g(): h() def h(): 1/0 def main(): e = mymod.call_pyfunc(f) #print(e) i(e) def i(e): j(e) def j(e): k(e) def k(e): mymod.reraise_pyexc(e) if __name__ == '__main__': main() ``` When running with CPython `mytest.py` prints traceback that includes _both_ i-j-k _and_ f-g-h: ``` (neo) (z-dev) (g.env) kirr@deco:~/src/tools/go/pygolang/x$ python mytest.py Traceback (most recent call last): File "mytest.py", line 19, in <module> main() File "mytest.py", line 12, in main i(e) File "mytest.py", line 14, in i def i(e): j(e) File "mytest.py", line 15, in j def j(e): k(e) File "mytest.py", line 16, in k def k(e): mymod.reraise_pyexc(e) File "mymod.pyx", line 33, in mymod.reraise_pyexc _pyexc_reraise(pyexc) File "mytest.py", line 5, in f def f(): g() File "mytest.py", line 6, in g def g(): h() File "mytest.py", line 7, in h def h(): 1/0 ZeroDivisionError: integer division or modulo by zero ``` However when run under PyPy \(I verified up till today’s nightly\) the traceback _does_ _not_ include f-g-h: ``` (pypy.venv) test1@deco:~/pypy/pygolang/x$ pypy mytest.py Traceback (most recent call last): File "mytest.py", line 19, in <module> main() File "mytest.py", line 12, in main i(e) File "mytest.py", line 14, in i def i(e): j(e) File "mytest.py", line 15, in j def j(e): k(e) File "mytest.py", line 16, in k def k(e): mymod.reraise_pyexc(e) File "mymod.pyx", line 33, in mymod.reraise_pyexc _pyexc_reraise(pyexc) ZeroDivisionError: integer division by zero ``` This minimal example models what happens in C/Pyx version of `sync.WorkGroup` in Pygolang and preserving the inner traceback is important there. ``` $ python Python 2.7.15+ (default, Feb 3 2019, 13:13:16) [GCC 8.2.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. ``` ``` (pypy.venv) test1@deco:~/pypy/pygolang/x$ pypy Python 2.7.13 (dcc4efe7355e, Nov 19 2019, 23:00:15) [PyPy 7.3.0-alpha0 with GCC 5.3.1 20160413] on linux2 Type "help", "copyright", "credits" or "license" for more information. And now for something completely different: ``pypy is like sausages'' ``` Thanks beforehand, Kirill /cc @{557058:7cb88866-fb18-487e-a2dc-b19de69f5f0b} _______________________________________________ pypy-issue mailing list pypy-issue@python.org https://mail.python.org/mailman/listinfo/pypy-issue