Commit 9e6ff8bd authored by Kirill Smelkov's avatar Kirill Smelkov

golang: Fix recover to clear current exception

Else the exception, even if it was recovered from, will be included as
cause for next raised exception. See added test for details.
parent bb9a94c3
...@@ -39,6 +39,7 @@ from golang._gopath import gimport # make gimport available from golang ...@@ -39,6 +39,7 @@ from golang._gopath import gimport # make gimport available from golang
import inspect, sys import inspect, sys
import decorator, six import decorator, six
from golang._golang import _pysys_exc_clear as _sys_exc_clear
# @func is a necessary decorator for functions for selected golang features to work. # @func is a necessary decorator for functions for selected golang features to work.
# #
...@@ -176,6 +177,7 @@ def recover(): ...@@ -176,6 +177,7 @@ def recover():
if exc is not None: if exc is not None:
goframe.recovered = True goframe.recovered = True
# recovered: clear current exception context # recovered: clear current exception context
_sys_exc_clear()
if six.PY2: if six.PY2:
goframe.exc_ctx = None goframe.exc_ctx = None
goframe.exc_ctx_tb = None goframe.exc_ctx_tb = None
......
...@@ -75,6 +75,33 @@ cdef extern from "golang/libgolang.h" nogil: ...@@ -75,6 +75,33 @@ cdef extern from "golang/libgolang.h" nogil:
const char *recover_ "golang::recover" () except + const char *recover_ "golang::recover" () except +
# pyrecover needs to clear sys.exc_info().
# py2 has sys.exc_clear() but it was removed in py3.
# provide our own _pysys_exc_clear().
cdef extern from *:
"""
static void XPySys_ExcClear() {
#if PY_VERSION_HEX >= 0x03000000 || defined(PYPY_VERSION)
PyErr_SetExcInfo(NULL, NULL, NULL);
#else
PyThreadState *ts;
PyObject *exc_type, *exc_value, *exc_traceback;
ts = PyThreadState_GET();
exc_type = ts->exc_type; ts->exc_type = NULL;
exc_value = ts->exc_value; ts->exc_value = NULL;
exc_traceback = ts->exc_traceback; ts->exc_traceback = NULL;
Py_XDECREF(exc_type);
Py_XDECREF(exc_value);
Py_XDECREF(exc_traceback);
#endif
}
"""
void XPySys_ExcClear()
def _pysys_exc_clear():
XPySys_ExcClear()
# ---- go ---- # ---- go ----
# go spawns lightweight thread. # go spawns lightweight thread.
......
...@@ -1295,6 +1295,46 @@ def test_defer_excchain(): ...@@ -1295,6 +1295,46 @@ def test_defer_excchain():
tb = Traceback(e.__traceback__) tb = Traceback(e.__traceback__)
assert tb[-1].name == "_" assert tb[-1].name == "_"
# verify that recover breaks exception chain.
@mark.xfail('PyPy' in sys.version and sys.version_info >= (3,),
reason="https://bitbucket.org/pypy/pypy/issues/3096")
def test_defer_excchain_vs_recover():
@func
def _():
def p1():
raise RuntimeError(1)
defer(p1)
def p2():
raise RuntimeError(2)
defer(p2)
def _():
r = recover()
assert r == "aaa"
defer(_)
defer(lambda: panic("aaa"))
with raises(RuntimeError) as exci:
_()
e1 = exci.value
assert type(e1) is RuntimeError
assert e1.args == (1,)
assert e1.__cause__ is None
assert e1.__context__ is not None
if six.PY3: # .__traceback__ of top-level exception
assert e1.__traceback__ is not None
tb1 = Traceback(e1.__traceback__)
assert tb1[-1].name == "p1"
e2 = e1.__context__
assert type(e2) is RuntimeError
assert e2.args == (2,)
assert e2.__cause__ is None
assert e2.__context__ is None # not chained to panic
assert e2.__traceback__ is not None
tb2 = Traceback(e2.__traceback__)
assert tb2[-1].name == "p2"
# verify that traceback.{print_exception,format_exception} work on chained # verify that traceback.{print_exception,format_exception} work on chained
# exception correctly. # exception correctly.
def test_defer_excchain_traceback(): def test_defer_excchain_traceback():
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment