You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There is a problem where Cython is not properly handling tstate->exc_* in exception handlers.
The "global" exception (tstate->exc_*) does not get cleared properly in an exception handler that raises a different exception.
Starting from an example, put the following into a pyx_leak.pyx file:
When you run this, you'll see that after calling bar, the TypeError exception is still set as the "global" exception.
The inner exception should not be leaking its way out (with normal Python, sys.exc_info() should return all None).
I'm not entirely certain what the correct way to handle this is. Looking at the generated code:
You can see that raising ValueError does a jump to __pyx_L7_except_error (and oddly, the next 4 lines are unreachable).
Then it jumps to __pyx_L1_error. I think this is the critical problem. If the __Pyx_ExceptionReset block of code was
executed, then I think (maybe) this problem would go away.
As a side note, I think this problem was introduced by the change mentioned in ticket http://trac.cython.org/ticket/228.
Previously, __Pyx_Raise would set tstate->exc_* to NULL (which in this case is the inner TypeError).
Technically, I don't think that's the correct behavior (doing the __Pyx_ExceptionReset after __Pyx_Raise seems more correct to me when I compare it to CPython's implementation).
This is a problem for long-lived Python functions that make calls into Cython code which raise exceptions.
The "global" exception stays live for much too long.
Just FYI, I've been researching this for a while. If you're curious, here's CPython's behavior in the exception handler:
PyTraceback_Here()
PyErr_Fetch() # curexc->local variable
set_exc_info()
tstate->exc_type = None
frame->f_exc_type = tstate->exc_type # Which is None
tstate->exc_* = local variable (TypeError)
do_raise()
PyErr_Restore()
tstate->curexc_* = ValueError
PyTraceback_Here()
reset_exc_info()
tstate->exc_* = frame->f_exc_* (which is None)
frame->f_exc_* = NULL
pop frame
Compared to Cython's behavior:
__Pyx_AddTraceback()
__Pyx_GetException()
Move tstate->curexc_* to tstate->exc_* (which is TypeError)
__Pyx_Raise()
__Pyx_ErrRestore()
tstate->curexc_* = ValueError
__Pyx_AddTraceback()
# NOTE: Critical error here, tstate->exc_* is still TypeError
Return NULL
I may take a look at a fix for this next week, but I am completely unfamiliar with how the compiler works, so I may not have time for it. I would really appreciate if anyone who is familiar with this to give any pointers or thoughts.
Cython doesn't have real frames, so it cannot just set the old exception value on function exit. There's code that keeps pointers to the original exception during try blocks, so that it can be reset afterwards. That's been in there for a while now and appears to work pretty well.
Cython needs to support both Py2 and Py3 correctly, meaning that it must set the exception context appropriately in Py3 and set up the global exception context as expected - but without changing the semantics of exception handling for Cython code. The problem you describe above is one where Py2 and Py3 behave differently due to the exception context.
I generally vote for Py3 semantics of exception raising and handling in Cython code, but we must map that to Py2.x and Py3 correctly under the hood. I've been working on the details for ages but never got around to finishing it up. Any help is appreciated.
There is a problem where Cython is not properly handling tstate->exc_* in exception handlers.
The "global" exception (tstate->exc_*) does not get cleared properly in an exception handler that raises a different exception.
Starting from an example, put the following into a pyx_leak.pyx file:
And then from .py (or the prompt), do this:
When you run this, you'll see that after calling bar, the
TypeError
exception is still set as the "global" exception.The inner exception should not be leaking its way out (with normal Python, sys.exc_info() should return all None).
I'm not entirely certain what the correct way to handle this is. Looking at the generated code:
You can see that raising
ValueError
does a jump to__pyx_L7_except_error
(and oddly, the next 4 lines are unreachable).Then it jumps to
__pyx_L1_error
. I think this is the critical problem. If the__Pyx_ExceptionReset
block of code wasexecuted, then I think (maybe) this problem would go away.
As a side note, I think this problem was introduced by the change mentioned in ticket http://trac.cython.org/ticket/228.
Previously,
__Pyx_Raise
would settstate->exc_*
to NULL (which in this case is the innerTypeError
).Technically, I don't think that's the correct behavior (doing the
__Pyx_ExceptionReset
after__Pyx_Raise
seems more correct to me when I compare it to CPython's implementation).This is a problem for long-lived Python functions that make calls into Cython code which raise exceptions.
The "global" exception stays live for much too long.
Just FYI, I've been researching this for a while. If you're curious, here's CPython's behavior in the exception handler:
Compared to Cython's behavior:
I may take a look at a fix for this next week, but I am completely unfamiliar with how the compiler works, so I may not have time for it. I would really appreciate if anyone who is familiar with this to give any pointers or thoughts.
Migrated from http://trac.cython.org/ticket/346
The text was updated successfully, but these errors were encountered: