Commit 69f3183a authored by Stefan Behnel's avatar Stefan Behnel

Improve handling of exception state in coroutines to prevent swallowing exceptions of the caller.

See #1731.
parent 7af5d3e9
...@@ -434,6 +434,7 @@ static int __pyx_Generator_init(void); /*proto*/ ...@@ -434,6 +434,7 @@ static int __pyx_Generator_init(void); /*proto*/
//@requires: Exceptions.c::PyThreadStateGet //@requires: Exceptions.c::PyThreadStateGet
//@requires: Exceptions.c::SwapException //@requires: Exceptions.c::SwapException
//@requires: Exceptions.c::RaiseException //@requires: Exceptions.c::RaiseException
//@requires: Exceptions.c::SaveResetException
//@requires: ObjectHandling.c::PyObjectCallMethod1 //@requires: ObjectHandling.c::PyObjectCallMethod1
//@requires: ObjectHandling.c::PyObjectGetAttrStr //@requires: ObjectHandling.c::PyObjectGetAttrStr
//@requires: CommonStructures.c::FetchCommonType //@requires: CommonStructures.c::FetchCommonType
...@@ -618,8 +619,9 @@ static void __Pyx__Coroutine_AlreadyTerminatedError(CYTHON_UNUSED PyObject *gen, ...@@ -618,8 +619,9 @@ static void __Pyx__Coroutine_AlreadyTerminatedError(CYTHON_UNUSED PyObject *gen,
static static
PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, int closing) { PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, int closing) {
PyObject *retval;
__Pyx_PyThreadState_declare __Pyx_PyThreadState_declare
PyObject *retval;
int restore_exc;
assert(!self->is_running); assert(!self->is_running);
...@@ -634,7 +636,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i ...@@ -634,7 +636,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i
} }
__Pyx_PyThreadState_assign __Pyx_PyThreadState_assign
if (value) { if (self->exc_type && self->exc_type != Py_None) {
#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON #if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
// FIXME: what to do in PyPy? // FIXME: what to do in PyPy?
#else #else
...@@ -649,10 +651,18 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i ...@@ -649,10 +651,18 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i
f->f_back = $local_tstate_cname->frame; f->f_back = $local_tstate_cname->frame;
} }
#endif #endif
// We were in an except handler when we left,
// restore the exception state which was put aside.
__Pyx_ExceptionSwap(&self->exc_type, &self->exc_value, __Pyx_ExceptionSwap(&self->exc_type, &self->exc_value,
&self->exc_traceback); &self->exc_traceback);
// self->exc_* now holds the exception state of the caller
restore_exc = 1;
} else { } else {
// save away the exception state of the caller
__Pyx_Coroutine_ExceptionClear(self); __Pyx_Coroutine_ExceptionClear(self);
__Pyx_ExceptionSave(&self->exc_type, &self->exc_value,
&self->exc_traceback);
restore_exc = 0;
} }
self->is_running = 1; self->is_running = 1;
...@@ -660,21 +670,23 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i ...@@ -660,21 +670,23 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value, i
self->is_running = 0; self->is_running = 0;
if (retval) { if (retval) {
// restore exception state of caller and save the state of the coroutine
__Pyx_ExceptionSwap(&self->exc_type, &self->exc_value, __Pyx_ExceptionSwap(&self->exc_type, &self->exc_value,
&self->exc_traceback); &self->exc_traceback);
}
#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON #if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
// FIXME: what to do in PyPy? // FIXME: what to do in PyPy?
#else #else
// Don't keep the reference to f_back any longer than necessary. It // Don't keep the reference to f_back any longer than necessary. It
// may keep a chain of frames alive or it could create a reference // may keep a chain of frames alive or it could create a reference
// cycle. // cycle.
if (self->exc_traceback) { if (restore_exc && self->exc_traceback) {
PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback; PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback;
PyFrameObject *f = tb->tb_frame; PyFrameObject *f = tb->tb_frame;
Py_CLEAR(f->f_back); Py_CLEAR(f->f_back);
} }
#endif #endif
} else { if (!restore_exc) {
__Pyx_Coroutine_ExceptionClear(self); __Pyx_Coroutine_ExceptionClear(self);
} }
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
import sys import sys
def _next(it):
if sys.version_info[0] >= 3:
return next(it)
else:
return it.next()
def test_generator_frame_cycle(): def test_generator_frame_cycle():
""" """
...@@ -23,8 +18,39 @@ def test_generator_frame_cycle(): ...@@ -23,8 +18,39 @@ def test_generator_frame_cycle():
finally: finally:
testit.append("I'm done") testit.append("I'm done")
g = whoo() g = whoo()
_next(g) next(g)
# Frame object cycle # Frame object cycle
eval('g.throw(ValueError)', {'g': g}) eval('g.throw(ValueError)', {'g': g})
del g del g
return tuple(testit)
def test_generator_frame_cycle_with_outer_exc():
"""
>>> test_generator_frame_cycle_with_outer_exc()
("I'm done",)
"""
testit = []
def whoo():
try:
yield
except:
yield
finally:
testit.append("I'm done")
g = whoo()
next(g)
try:
raise ValueError()
except ValueError as exc:
assert sys.exc_info()[1] is exc, sys.exc_info()
# Frame object cycle
eval('g.throw(ValueError)', {'g': g})
assert sys.exc_info()[1] is exc, sys.exc_info()
del g
assert sys.exc_info()[1] is exc, sys.exc_info()
return tuple(testit) return tuple(testit)
# mode: run
# ticket: gh1731
def cygen():
yield 1
def test_from_cython(g):
"""
>>> def pygen(): yield 1
>>> test_from_cython(pygen)
Traceback (most recent call last):
ZeroDivisionError: integer division or modulo by zero
>>> test_from_cython(cygen)
Traceback (most recent call last):
ZeroDivisionError: integer division or modulo by zero
"""
try:
1 / 0
except:
for _ in g():
pass
raise
def test_from_python():
"""
>>> def test(g):
... try:
... 1 / 0
... except:
... for _ in g():
... pass
... raise
>>> def pygen():
... yield 1
>>> test(pygen) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division ...by zero
>>> test(cygen) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division ...by zero
"""
def test_from_console():
"""
>>> def pygen(): yield 1
>>> try: # doctest: +ELLIPSIS
... 1 / 0
... except:
... for _ in pygen():
... pass
... raise
Traceback (most recent call last):
ZeroDivisionError: ...division ...by zero
>>> try: # doctest: +ELLIPSIS
... 1 / 0
... except:
... for _ in cygen():
... pass
... raise
Traceback (most recent call last):
ZeroDivisionError: ...division ...by zero
"""
# mode: run # mode: run
# tag: generators # tag: generators
import sys
import cython import cython
...@@ -147,25 +148,34 @@ def check_throw(): ...@@ -147,25 +148,34 @@ def check_throw():
except ValueError: except ValueError:
pass pass
def check_yield_in_except(): def check_yield_in_except():
""" """
>>> import sys >>> try:
>>> orig_exc = sys.exc_info()[0] ... raise TypeError("RAISED !")
>>> g = check_yield_in_except() ... except TypeError as orig_exc:
>>> next(g) ... assert isinstance(orig_exc, TypeError), orig_exc
>>> next(g) ... g = check_yield_in_except()
>>> orig_exc is sys.exc_info()[0] or sys.exc_info()[0] ... print(orig_exc is sys.exc_info()[1] or sys.exc_info())
... next(g)
... print(orig_exc is sys.exc_info()[1] or sys.exc_info())
... next(g)
... print(orig_exc is sys.exc_info()[1] or sys.exc_info())
True
True
True True
""" """
try: try:
yield yield
raise ValueError raise ValueError
except ValueError: except ValueError as exc:
assert sys.exc_info()[1] is exc, sys.exc_info()
yield yield
assert sys.exc_info()[1] is exc, sys.exc_info()
def yield_in_except_throw_exc_type(): def yield_in_except_throw_exc_type():
""" """
>>> import sys
>>> g = yield_in_except_throw_exc_type() >>> g = yield_in_except_throw_exc_type()
>>> next(g) >>> next(g)
>>> g.throw(TypeError) >>> g.throw(TypeError)
...@@ -177,12 +187,14 @@ def yield_in_except_throw_exc_type(): ...@@ -177,12 +187,14 @@ def yield_in_except_throw_exc_type():
""" """
try: try:
raise ValueError raise ValueError
except ValueError: except ValueError as exc:
assert sys.exc_info()[1] is exc, sys.exc_info()
yield yield
assert sys.exc_info()[1] is exc, sys.exc_info()
def yield_in_except_throw_instance(): def yield_in_except_throw_instance():
""" """
>>> import sys
>>> g = yield_in_except_throw_instance() >>> g = yield_in_except_throw_instance()
>>> next(g) >>> next(g)
>>> g.throw(TypeError()) >>> g.throw(TypeError())
...@@ -194,8 +206,11 @@ def yield_in_except_throw_instance(): ...@@ -194,8 +206,11 @@ def yield_in_except_throw_instance():
""" """
try: try:
raise ValueError raise ValueError
except ValueError: except ValueError as exc:
assert sys.exc_info()[1] is exc, sys.exc_info()
yield yield
assert sys.exc_info()[1] is exc, sys.exc_info()
def test_swap_assignment(): def test_swap_assignment():
""" """
......
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